diff --git a/.rwx/ci.yml b/.rwx/ci.yml index 5547e13..382faa8 100644 --- a/.rwx/ci.yml +++ b/.rwx/ci.yml @@ -103,11 +103,25 @@ tasks: -- \ -ldflags "-w -s -X github.com/rwx-cloud/cli/cmd/rwx/config.Version=testing-${{ init.commit-sha }}" \ -parallel 4 \ - ./internal/... ./cmd/... ./test/... + $(go list ./internal/... ./cmd/... ./test/... | grep -v /captain/) outputs: test-results: - path: tmp/go-test.json + # TODO: Once internal/captain/ no longer uses Ginkgo, remove this task + # and add captain packages back to unit-tests above. + - key: captain-tests + use: [go-deps, gotestsum] + run: | + gotestsum \ + --jsonfile tmp/go-test-captain.json \ + -- \ + -ldflags "-w -s -X github.com/rwx-cloud/cli/cmd/rwx/config.Version=testing-${{ init.commit-sha }}" \ + ./internal/captain/... + outputs: + test-results: + - path: tmp/go-test-captain.json + - key: rwx-cli use: [go-deps, lsp-server] run: | diff --git a/cmd/rwx/env.go b/cmd/rwx/env.go new file mode 100644 index 0000000..3ed3211 --- /dev/null +++ b/cmd/rwx/env.go @@ -0,0 +1,12 @@ +package main + +import "os" + +// getEnvWithFallback checks the primary env var first, falling back to the +// legacy name for backwards compatibility during the CAPTAIN_ → RWX_TEST_ migration. +func getEnvWithFallback(primary, fallback string) string { + if v := os.Getenv(primary); v != "" { + return v + } + return os.Getenv(fallback) +} diff --git a/cmd/rwx/identity_recipes.json b/cmd/rwx/identity_recipes.json new file mode 100644 index 0000000..f0d55b8 --- /dev/null +++ b/cmd/rwx/identity_recipes.json @@ -0,0 +1,95 @@ +[ + { + "language": ".NET", + "kind": "xUnit", + "recipe": { "components": ["assembly", "description"], "strict": true } + }, + { + "language": "Elixir", + "kind": "ExUnit", + "recipe": { "components": ["file", "description"], "strict": true } + }, + { + "language": "Go", + "kind": "Ginkgo", + "recipe": { "components": ["file", "description"], "strict": true } + }, + { + "language": "Go", + "kind": "go test", + "recipe": { "components": ["package", "description"], "strict": true } + }, + { + "language": "JavaScript", + "kind": "Cucumber", + "recipe": { "components": ["file", "description"], "strict": true } + }, + { + "language": "JavaScript", + "kind": "Cypress", + "recipe": { "components": ["description"], "strict": true } + }, + { + "language": "JavaScript", + "kind": "Jest", + "recipe": { "components": ["file", "description"], "strict": true } + }, + { + "language": "JavaScript", + "kind": "Karma", + "recipe": { "components": ["browserName", "description"], "strict": true } + }, + { + "language": "JavaScript", + "kind": "Mocha", + "recipe": { "components": ["file", "description"], "strict": true } + }, + { + "language": "JavaScript", + "kind": "Playwright", + "recipe": { + "components": ["project", "file", "description"], + "strict": true + } + }, + { + "language": "JavaScript", + "kind": "Vitest", + "recipe": { "components": ["file", "description"], "strict": true } + }, + { + "language": "PHP", + "kind": "PHPUnit", + "recipe": { "components": ["file", "description"], "strict": true } + }, + { + "language": "Python", + "kind": "pytest", + "recipe": { "components": ["file", "description"], "strict": true } + }, + { + "language": "Python", + "kind": "unittest", + "recipe": { "components": ["file", "description"], "strict": true } + }, + { + "language": "Ruby", + "kind": "Cucumber", + "recipe": { "components": ["file", "description"], "strict": true } + }, + { + "language": "Ruby", + "kind": "minitest", + "recipe": { "components": ["file", "description"], "strict": true } + }, + { + "language": "Ruby", + "kind": "RSpec", + "recipe": { "components": ["file", "description"], "strict": true } + }, + { + "language": "other", + "kind": "other", + "recipe": { "components": ["file", "description"], "strict": false } + } +] diff --git a/cmd/rwx/main.go b/cmd/rwx/main.go index 0ad3b15..52e67fe 100644 --- a/cmd/rwx/main.go +++ b/cmd/rwx/main.go @@ -5,6 +5,7 @@ import ( "fmt" "os" + captainerrors "github.com/rwx-cloud/cli/internal/captain/errors" "github.com/rwx-cloud/cli/internal/cli" ) @@ -18,6 +19,11 @@ func main() { return } + // Captain's ExecutionError carries custom exit codes from subprocess execution + if e, ok := captainerrors.AsExecutionError(err); ok { + os.Exit(e.Code) + } + if !errors.Is(err, HandledError) { if Debug { // Enabling debug output will print stacktraces diff --git a/cmd/rwx/root.go b/cmd/rwx/root.go index ddc4bae..e9de0dd 100644 --- a/cmd/rwx/root.go +++ b/cmd/rwx/root.go @@ -50,6 +50,7 @@ var ( accessTokenBackend = accesstoken.NewFileBackend(fileBackend) docsTokenBackend := docstoken.NewFileBackend(fileBackend) + versionsBackend := versions.NewFileBackend(fileBackend) c, err := api.NewClient(api.Config{AccessToken: AccessToken, Host: rwxHost, AccessTokenBackend: accessTokenBackend, VersionsBackend: versionsBackend}) @@ -98,6 +99,17 @@ var ( } ) +func initAccessTokenBackend() (accesstoken.Backend, error) { + fileBackend, err := internalconfig.NewFileBackend([]string{ + filepath.Join("~", ".config", "rwx"), + filepath.Join("~", ".mint"), + }) + if err != nil { + return nil, errors.Wrap(err, "unable to initialize config backend") + } + return accesstoken.NewFileBackend(fileBackend), nil +} + func addRwxDirFlag(cmd *cobra.Command) { cmd.Flags().StringVarP(&RwxDirectory, "dir", "d", "", "the directory your RWX configuration files are located in, typically `.rwx`. By default, the CLI traverses up until it finds a `.rwx` directory.") } @@ -159,6 +171,7 @@ func init() { rootCmd.AddCommand(vaultsCmd) rootCmd.AddCommand(docsCmd) rootCmd.AddCommand(resultsCmd) + rootCmd.AddCommand(testCmd) rootCmd.AddCommand(whoamiCmd) cobra.OnInitialize(func() { diff --git a/cmd/rwx/test.go b/cmd/rwx/test.go new file mode 100644 index 0000000..1710546 --- /dev/null +++ b/cmd/rwx/test.go @@ -0,0 +1,601 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/spf13/cobra" + + captaincli "github.com/rwx-cloud/cli/internal/captain/cli" + "github.com/rwx-cloud/cli/internal/captain/config" + captainerrors "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/mint" + "github.com/rwx-cloud/cli/internal/captain/providers" + "github.com/rwx-cloud/cli/internal/captain/reporting" + "github.com/rwx-cloud/cli/internal/captain/runpartition" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type testRootCliArgs struct { + configFilePath string + debug bool + insecure bool + suiteID string + positionalArgs []string +} + +type testFrameworkParams struct { + kind string + language string +} + +type testCliArgs struct { + command string + testResults string + failOnUploadError bool + failOnDuplicateTestID bool + failOnMisconfiguredRetry bool + failRetriesFast bool + flakyRetries int + intermediateArtifactsPath string + additionalArtifactPaths []string + maxTestsToRetry string + postRetryCommands []string + preRetryCommands []string + printSummary bool + quiet bool + reporters []string + retries int + retryCommandTemplate string + genericProvider providers.GenericEnv + frameworkParams testFrameworkParams + rootCliArgs testRootCliArgs + partitionIndex int + partitionTotal int + partitionDelimiter string + partitionCommandTemplate string + partitionGlobs []string + partitionRoundRobin bool + partitionTrimPrefix string + quarantinedTestRetries int +} + +var testArgs = testCliArgs{} + +var testCmd = &cobra.Command{ + Use: "test", + Short: "Manage test suites", + Hidden: true, + // Override root's PersistentPreRunE which initializes Docker, SSH, Git, + // and API clients that test commands don't need. Each test subcommand + // handles its own initialization via PreRunE. + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return nil + }, +} + +func init() { + configureTestRootFlags(testCmd, &testArgs) + + testRunCmd := createTestRunCmd(&testArgs) + if err := addTestRunFlags(testRunCmd, &testArgs); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + testRunCmd.SetHelpTemplate(testHelpTemplate) + testRunCmd.SetUsageTemplate(testShortUsageTemplate) + testCmd.AddCommand(testRunCmd) + + configureTestPartitionCmd(testCmd, &testArgs) + configureTestResultsCmd(testCmd, &testArgs) +} + +func configureTestRootFlags(cmd *cobra.Command, cliArgs *testCliArgs) { + cmd.PersistentFlags().StringVar(&cliArgs.rootCliArgs.configFilePath, "config-file", "", "the config file for rwx test") + + suiteIDFromEnv := getEnvWithFallback("RWX_TEST_SUITE_ID", "CAPTAIN_SUITE_ID") + cmd.PersistentFlags().StringVar(&cliArgs.rootCliArgs.suiteID, "suite-id", suiteIDFromEnv, "the id of the test suite") + + cmd.PersistentFlags().BoolVar(&cliArgs.rootCliArgs.debug, "test-debug", false, "enable debug output for test commands") + _ = cmd.PersistentFlags().MarkHidden("test-debug") + + cmd.PersistentFlags().BoolVar(&cliArgs.rootCliArgs.insecure, "insecure", false, "disable TLS for the API") + _ = cmd.PersistentFlags().MarkHidden("insecure") + + cmd.PersistentFlags().BoolVarP(&cliArgs.quiet, "quiet", "q", false, "disables most default output") +} + +func extractTestSuiteID(rootArgs *testRootCliArgs, args []string) error { + rootArgs.positionalArgs = args + if rootArgs.suiteID != "" { + return nil + } + + if len(rootArgs.positionalArgs) == 0 { + return captainerrors.NewInputError("required flag \"suite-id\" not set") + } + + rootArgs.suiteID = rootArgs.positionalArgs[0] + rootArgs.positionalArgs = rootArgs.positionalArgs[1:] + + return nil +} + +func bindTestRootFlags(cfg testConfig, rootArgs testRootCliArgs) testConfig { + if rootArgs.debug { + cfg.Output.Debug = true + } + + if rootArgs.insecure { + cfg.Cloud.Insecure = true + } + + return cfg +} + +func addTestFrameworkFlags(command *cobra.Command, fp *testFrameworkParams) { + formattedKnownFrameworks := make([]string, len(v1.KnownFrameworks)) + for i, framework := range v1.KnownFrameworks { + formattedKnownFrameworks[i] = fmt.Sprintf(" --language %v --framework %v", framework.Language, framework.Kind) + } + + command.Flags().StringVar( + &fp.language, + "language", + "", + fmt.Sprintf( + "The programming language of the test suite (required if framework is set).\n"+ + "These can be set to anything, but rwx test has specific handling for these combinations:\n%v", + strings.Join(formattedKnownFrameworks, "\n"), + ), + ) + command.Flags().StringVar( + &fp.kind, + "framework", + "", + fmt.Sprintf( + "The framework of the test suite (required if language is set).\n"+ + "These can be set to anything, but rwx test has specific handling for these combinations:\n%v", + strings.Join(formattedKnownFrameworks, "\n"), + ), + ) +} + +func bindTestFrameworkFlags(cfg testConfig, fp testFrameworkParams, suiteID string) testConfig { + if suiteConfig, ok := cfg.TestSuites[suiteID]; ok { + if fp.kind != "" { + suiteConfig.Results.Framework = fp.kind + } + + if fp.language != "" { + suiteConfig.Results.Language = fp.language + } + + cfg.TestSuites[suiteID] = suiteConfig + } + + return cfg +} + +func addTestShaFlag(cmd *cobra.Command, destination *string) { + cmd.Flags().StringVar( + destination, + "sha", + getEnvWithFallback("RWX_TEST_SHA", "CAPTAIN_SHA"), + "the git commit sha hash of the commit being built", + ) +} + +func addTestGenericProviderFlags(cmd *cobra.Command, destination *providers.GenericEnv) { + cmd.Flags().StringVar( + &destination.Branch, + "branch", + getEnvWithFallback("RWX_TEST_BRANCH", "CAPTAIN_BRANCH"), + "the branch name of the commit being built\n"+ + "if using a supported CI provider, this will be automatically set\n"+ + "otherwise use this flag or set the environment variable RWX_TEST_BRANCH\n", + ) + + cmd.Flags().StringVar( + &destination.Who, + "who", + getEnvWithFallback("RWX_TEST_WHO", "CAPTAIN_WHO"), + "the person who triggered the build\n"+ + "if using a supported CI provider, this will be automatically set\n"+ + "otherwise use this flag or set the environment variable RWX_TEST_WHO\n", + ) + + addTestShaFlag(cmd, &destination.Sha) + + cmd.Flags().StringVar( + &destination.CommitMessage, + "commit-message", + getEnvWithFallback("RWX_TEST_COMMIT_MESSAGE", "CAPTAIN_COMMIT_MESSAGE"), + "the git commit message of the commit being built\n"+ + "if using a supported CI provider, this will be automatically set\n"+ + "otherwise use this flag or set the environment variable RWX_TEST_COMMIT_MESSAGE\n", + ) + + cmd.Flags().StringVar( + &destination.BuildURL, + "build-url", + getEnvWithFallback("RWX_TEST_BUILD_URL", "CAPTAIN_BUILD_URL"), + "the URL of the build results\n"+ + "if using a supported CI provider, this will be automatically set\n"+ + "otherwise use this flag or set the environment variable RWX_TEST_BUILD_URL\n", + ) + + cmd.Flags().StringVar( + &destination.Title, + "title", + getEnvWithFallback("RWX_TEST_TITLE", "CAPTAIN_TITLE"), + "a descriptive title for the test suite run, such as the commit message or build message\n"+ + "if using a supported CI provider, this will be automatically set\n"+ + "otherwise use this flag or set the environment variable RWX_TEST_TITLE\n", + ) +} + +func createTestRunCmd(cliArgs *testCliArgs) *cobra.Command { + return &cobra.Command{ + Use: "run [flags] --suite-id= ", + Short: "Execute a test suite", + Long: "'rwx test run' can be used to execute a test suite and optionally upload the resulting artifacts.", + Example: ` rwx test run --suite-id="your-project-rake" -c "bundle exec rake"` + "\n" + + ` rwx test run --suite-id="your-project-jest" --test-results "jest-result.json" -c jest`, + PreRunE: initTestService(cliArgs, providers.Validate), + RunE: func(cmd *cobra.Command, _ []string) error { + err := func() error { + args := cliArgs.rootCliArgs.positionalArgs + + reporterFuncs := make(map[string]captaincli.Reporter) + + cfg, err := getTestConfig(cmd) + if err != nil { + return captainerrors.WithStack(err) + } + + captain, err := captaincli.GetService(cmd) + if err != nil { + return captainerrors.WithStack(err) + } + + var runConfig captaincli.RunConfig + if suiteConfig, ok := cfg.TestSuites[cliArgs.rootCliArgs.suiteID]; ok { + for name, path := range suiteConfig.Output.Reporters { + switch name { + case "rwx-v1-json": + reporterFuncs[path] = reporting.WriteJSONSummary + case "junit-xml": + reporterFuncs[path] = reporting.WriteJUnitSummary + case "markdown-summary": + reporterFuncs[path] = reporting.WriteMarkdownSummary + case "github-step-summary": + stepSummaryPath := os.Getenv("GITHUB_STEP_SUMMARY") + if stepSummaryPath == "" { + captain.Log.Debug( + "Skipping configuration of the 'github-step-summary' reporter " + + "(the 'GITHUB_STEP_SUMMARY' environment variable is not set).", + ) + continue + } + + reporterFuncs[stepSummaryPath] = reporting.WriteMarkdownSummary + default: + return captainerrors.NewConfigurationError( + fmt.Sprintf("Unknown reporter %q", name), + "Available reporters are 'rwx-v1-json', 'junit-xml', 'markdown-summary', and 'github-step-summary'.", + "", + ) + } + } + + rwxTestResultsDir := os.Getenv("RWX_TEST_RESULTS") + if rwxTestResultsDir != "" { + if _, err := os.Stat(rwxTestResultsDir); err != nil { + captain.Log.Warnf("RWX_TEST_RESULTS directory does not exist: %s", rwxTestResultsDir) + } else { + rwxOutputPath := filepath.Join(rwxTestResultsDir, cliArgs.rootCliArgs.suiteID+".json") + + rwxAbsPath, err := filepath.Abs(rwxOutputPath) + if err != nil { + captain.Log.Warnf("Unable to resolve absolute path for RWX_TEST_RESULTS output: %s", err.Error()) + } else { + isDuplicate := false + for existingPath := range reporterFuncs { + existingAbsPath, err := filepath.Abs(existingPath) + if err == nil && existingAbsPath == rwxAbsPath { + isDuplicate = true + break + } + } + + if !isDuplicate { + reporterFuncs[rwxOutputPath] = reporting.WriteJSONSummary + } + } + } + } + + partitionIndex := cliArgs.partitionIndex + partitionTotal := cliArgs.partitionTotal + provider, err := cfg.ProvidersEnv.MakeProvider() + if err != nil { + return captainerrors.Wrap(err, "failed to construct provider") + } + + if partitionIndex < 0 { + partitionIndex = provider.PartitionNodes.Index + } + + if partitionTotal < 0 { + partitionTotal = provider.PartitionNodes.Total + } + + if suiteConfig.Retries.MaxTests == "" && suiteConfig.Retries.MaxTestsLegacyName != "" { + suiteConfig.Retries.MaxTests = suiteConfig.Retries.MaxTestsLegacyName + } + + runConfig = captaincli.RunConfig{ + Args: args, + CloudOrganizationSlug: "deep_link", + Command: suiteConfig.Command, + FailOnUploadError: suiteConfig.FailOnUploadError, + FailOnMisconfiguredRetry: suiteConfig.Retries.FailOnMisconfiguration, + FailRetriesFast: suiteConfig.Retries.FailFast, + FlakyRetries: suiteConfig.Retries.FlakyAttempts, + IntermediateArtifactsPath: suiteConfig.Retries.IntermediateArtifactsPath, + AdditionalArtifactPaths: suiteConfig.Retries.AdditionalArtifactPaths, + MaxTestsToRetry: suiteConfig.Retries.MaxTests, + PostRetryCommands: suiteConfig.Retries.PostRetryCommands, + PreRetryCommands: suiteConfig.Retries.PreRetryCommands, + PrintSummary: suiteConfig.Output.PrintSummary, + Quiet: suiteConfig.Output.Quiet, + Reporters: reporterFuncs, + Retries: suiteConfig.Retries.Attempts, + RetryCommandTemplate: suiteConfig.Retries.Command, + QuarantinedTestRetries: suiteConfig.Retries.QuarantinedAttempts, + SubstitutionsByFramework: targetedretries.SubstitutionsByFramework, + SuiteID: cliArgs.rootCliArgs.suiteID, + TestResultsFileGlob: os.ExpandEnv(suiteConfig.Results.Path), + + UploadResults: true, + PartitionCommandTemplate: suiteConfig.Partition.Command, + PartitionConfig: captaincli.PartitionConfig{ + SuiteID: cliArgs.rootCliArgs.suiteID, + TestFilePaths: suiteConfig.Partition.Globs, + PartitionNodes: config.PartitionNodes{ + Index: partitionIndex, + Total: partitionTotal, + }, + Delimiter: suiteConfig.Partition.Delimiter, + RoundRobin: suiteConfig.Partition.RoundRobin, + TrimPrefix: suiteConfig.Partition.TrimPrefix, + }, + PartitionRoundRobin: suiteConfig.Partition.RoundRobin, + PartitionTrimPrefix: suiteConfig.Partition.TrimPrefix, + WriteRetryFailedTestsAction: mint.IsMint(), + DidRetryFailedTestsInMint: mint.DidRetryFailedTests(), + } + } + + err = captain.RunSuite(cmd.Context(), runConfig) + if _, ok := captainerrors.AsConfigurationError(err); !ok { + cmd.SilenceUsage = true + } + + return captainerrors.WithStack(err) + }() + if err != nil { + return captainerrors.WithDecoration(err) + } + return nil + }, + } +} + +func addTestRunFlags(runCmd *cobra.Command, cliArgs *testCliArgs) error { + runCmd.Flags().StringVarP(&cliArgs.command, "command", "c", "", "the command to run") + runCmd.Flags().StringVar(&cliArgs.testResults, "test-results", "", "a filepath to a test result - supports globs for multiple result files") + runCmd.Flags().BoolVar(&cliArgs.failOnUploadError, "fail-on-upload-error", false, "return a non-zero exit code in case the test results upload fails") + runCmd.Flags().BoolVar(&cliArgs.failOnDuplicateTestID, "fail-on-duplicate-test-id", false, "return a non-zero exit code in case the identifiers in test results are not unique") + runCmd.Flags().BoolVar(&cliArgs.failOnMisconfiguredRetry, "fail-on-misconfigured-retry", false, "return a non-zero exit code in case the retry command isn't producing the expect test result output") + runCmd.Flags().StringVar(&cliArgs.intermediateArtifactsPath, "intermediate-artifacts-path", "", "the path to store intermediate artifacts under. Intermediate artifacts will be removed if not set.") + runCmd.Flags().StringArrayVar(&cliArgs.additionalArtifactPaths, "additional-artifact-paths", []string{}, "additional artifact paths (globs) to preserve across retries. Requires --intermediate-artifacts-path to be set.") + runCmd.Flags().StringArrayVar(&cliArgs.postRetryCommands, "post-retry", []string{}, "commands to run immediately after rwx test retries a test") + runCmd.Flags().StringArrayVar(&cliArgs.preRetryCommands, "pre-retry", []string{}, "commands to run immediately before rwx test retries a test") + runCmd.Flags().BoolVar(&cliArgs.printSummary, "print-summary", false, "prints a summary of all tests to the console") + runCmd.Flags().StringArrayVar(&cliArgs.reporters, "reporter", []string{}, "one or more `type=output_path` pairs to enable different reporting options.\nAvailable reporters are 'rwx-v1-json', 'junit-xml', 'markdown-summary', and 'github-step-summary'.") + runCmd.Flags().IntVar(&cliArgs.retries, "retries", -1, "the number of times failed tests should be retried (e.g. --retries 2 would mean a maximum of 3 attempts of any given test)") + runCmd.Flags().IntVar(&cliArgs.flakyRetries, "flaky-retries", -1, "the number of times failing flaky tests should be retried (takes precedence over --retries if the test is known to be flaky)") + runCmd.Flags().StringVar(&cliArgs.maxTestsToRetry, "max-tests-to-retry", "", "if set, retries will not be run when there are more than N tests to retry or if more than N%% of all tests need retried") + runCmd.Flags().BoolVar(&cliArgs.failRetriesFast, "fail-retries-fast", false, "if set, your test suite will fail as quickly as we know it will fail") + runCmd.Flags().IntVar(&cliArgs.quarantinedTestRetries, "quarantined-test-retries", -1, "number of retries for quarantined tests, similar to --flaky-retries. Set to 0 to disable retrying quarantined tests") + runCmd.Flags().IntVar(&cliArgs.partitionIndex, "partition-index", -1, "The 0-indexed index of a particular partition") + runCmd.Flags().IntVar(&cliArgs.partitionTotal, "partition-total", -1, "The desired number of partitions. Any empty partitions will result in a noop.") + runCmd.Flags().StringVar(&cliArgs.partitionDelimiter, "partition-delimiter", " ", "The delimiter used to separate partitioned files.") + runCmd.Flags().StringArrayVar(&cliArgs.partitionGlobs, "partition-globs", []string{}, "Filepath globs used to identify the test files you wish to partition") + runCmd.Flags().StringVar(&cliArgs.partitionCommandTemplate, "partition-command", "", + fmt.Sprintf( + "The command that will be run to execute a subset of your tests while partitioning\n"+ + "(required if --partition-index or --partition-total is passed)\n"+ + "Examples:\n Custom: --partition-command \"%v\"", + runpartition.DelimiterSubstitution{}.Example(), + ), + ) + runCmd.Flags().BoolVar(&cliArgs.partitionRoundRobin, "partition-round-robin", false, "Whether to naively round robin tests across partitions. When false, historical test timing data will be used to evenly balance the partitions.") + runCmd.Flags().StringVar(&cliArgs.partitionTrimPrefix, "partition-trim-prefix", "", "A prefix to trim from the beginning of local test file paths when comparing them to historical timing data.") + + formattedSubstitutionExamples := make([]string, len(targetedretries.SubstitutionsByFramework)) + i := 0 + for framework, substitution := range targetedretries.SubstitutionsByFramework { + formattedSubstitutionExamples[i] = fmt.Sprintf(" %v: --retry-command \"%v\"", framework, substitution.Example()) + i++ + } + sort.SliceStable(formattedSubstitutionExamples, func(i, j int) bool { + return strings.ToLower(formattedSubstitutionExamples[i]) < strings.ToLower(formattedSubstitutionExamples[j]) + }) + + runCmd.Flags().StringVar( + &cliArgs.retryCommandTemplate, + "retry-command", + "", + fmt.Sprintf( + "the command that will be run to execute a subset of your tests while retrying "+ + "(required if --retries or --flaky-retries is passed)\n"+ + "Examples:\n Custom: --retry-command \"%v\"\n%v", + targetedretries.JSONSubstitution{}.Example(), + strings.Join(formattedSubstitutionExamples, "\n"), + ), + ) + + addTestGenericProviderFlags(runCmd, &cliArgs.genericProvider) + addTestFrameworkFlags(runCmd, &cliArgs.frameworkParams) + return nil +} + +func bindTestRunFlags(cfg testConfig, cliArgs testCliArgs, cmd *cobra.Command) testConfig { + if suiteConfig, ok := cfg.TestSuites[cliArgs.rootCliArgs.suiteID]; ok { + if cliArgs.command != "" { + suiteConfig.Command = cliArgs.command + } + if cliArgs.failOnUploadError { + suiteConfig.FailOnUploadError = true + } + if cliArgs.failOnDuplicateTestID { + suiteConfig.FailOnDuplicateTestID = true + } + if cliArgs.failOnMisconfiguredRetry { + suiteConfig.Retries.FailOnMisconfiguration = true + } + if cliArgs.testResults != "" { + suiteConfig.Results.Path = cliArgs.testResults + } + if len(cliArgs.postRetryCommands) != 0 { + suiteConfig.Retries.PostRetryCommands = cliArgs.postRetryCommands + } + if len(cliArgs.preRetryCommands) != 0 { + suiteConfig.Retries.PreRetryCommands = cliArgs.preRetryCommands + } + if cliArgs.failRetriesFast { + suiteConfig.Retries.FailFast = true + } + if suiteConfig.Retries.QuarantinedAttempts == 0 || cliArgs.quarantinedTestRetries != -1 { + suiteConfig.Retries.QuarantinedAttempts = cliArgs.quarantinedTestRetries + } + if suiteConfig.Retries.FlakyAttempts == 0 || cliArgs.flakyRetries != -1 { + suiteConfig.Retries.FlakyAttempts = cliArgs.flakyRetries + } + if cliArgs.maxTestsToRetry != "" { + suiteConfig.Retries.MaxTests = cliArgs.maxTestsToRetry + } + if cliArgs.printSummary { + suiteConfig.Output.PrintSummary = true + } + if cliArgs.quiet { + suiteConfig.Output.Quiet = true + } + if len(cliArgs.reporters) > 0 { + reporterConfig := suiteConfig.Output.Reporters + if reporterConfig == nil { + reporterConfig = make(map[string]string) + } + for _, r := range cliArgs.reporters { + name, path, _ := strings.Cut(r, "=") + reporterConfig[name] = path + } + suiteConfig.Output.Reporters = reporterConfig + } + if suiteConfig.Retries.Attempts == 0 || cliArgs.retries != -1 { + suiteConfig.Retries.Attempts = cliArgs.retries + } + if cliArgs.retryCommandTemplate != "" { + suiteConfig.Retries.Command = cliArgs.retryCommandTemplate + } + if cliArgs.intermediateArtifactsPath != "" { + suiteConfig.Retries.IntermediateArtifactsPath = cliArgs.intermediateArtifactsPath + } + if len(cliArgs.additionalArtifactPaths) > 0 { + suiteConfig.Retries.AdditionalArtifactPaths = cliArgs.additionalArtifactPaths + } + if suiteConfig.Partition.Delimiter == "" { + suiteConfig.Partition.Delimiter = cliArgs.partitionDelimiter + } + if cliArgs.partitionCommandTemplate != "" { + suiteConfig.Partition.Command = cliArgs.partitionCommandTemplate + } + if len(cliArgs.partitionGlobs) != 0 { + suiteConfig.Partition.Globs = cliArgs.partitionGlobs + } + if cmd.Flags().Changed("partition-round-robin") { + suiteConfig.Partition.RoundRobin = cliArgs.partitionRoundRobin + } + if cmd.Flags().Changed("partition-trim-prefix") { + suiteConfig.Partition.TrimPrefix = cliArgs.partitionTrimPrefix + } + + cfg.TestSuites[cliArgs.rootCliArgs.suiteID] = suiteConfig + cfg.ProvidersEnv.Generic = providers.MergeGeneric(cfg.ProvidersEnv.Generic, cliArgs.genericProvider) + } + + return cfg +} + +// Help templates for the run subcommand +const testHelpTemplate = `{{with (or .Long .Short)}}{{. | trimTrailingWhitespaces}}{{end}} + +Usage:{{if .Runnable}} + {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} + {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} + +Aliases: + {{.NameAndAliases}}{{end}}{{if .HasExample}} + +Examples: +{{.Example}}{{end}}{{if .HasAvailableSubCommands}}{{$cmds := .Commands}}{{if eq (len .Groups) 0}} + +Available Commands:{{range $cmds}}{{if (or .IsAvailableCommand (eq .Name "help"))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{else}}{{range $group := .Groups}} + +{{.Title}}{{range $cmds}}{{if (and (eq .GroupID $group.ID) (or .IsAvailableCommand (eq .Name "help")))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if not .AllChildCommandsHaveGroup}} + +Additional Commands:{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} + +Flags: +{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} + +Global Flags: +{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} + +Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} + {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} + +Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} +` + +const testShortUsageTemplate = `Usage:{{if .Runnable}} + {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} + {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} + +Aliases: + {{.NameAndAliases}}{{end}}{{if .HasExample}} + +Examples: +{{.Example}}{{end}}{{if .HasAvailableSubCommands}}{{$cmds := .Commands}}{{if eq (len .Groups) 0}} + +Available Commands:{{range $cmds}}{{if (or .IsAvailableCommand (eq .Name "help"))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{else}}{{range $group := .Groups}} + +{{.Title}}{{range $cmds}}{{if (and (eq .GroupID $group.ID) (or .IsAvailableCommand (eq .Name "help")))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if not .AllChildCommandsHaveGroup}} + +Additional Commands:{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}} + +Run "{{.CommandPath}} --help" to see all available flags for this command. +` diff --git a/cmd/rwx/test_config.go b/cmd/rwx/test_config.go new file mode 100644 index 0000000..f64df5a --- /dev/null +++ b/cmd/rwx/test_config.go @@ -0,0 +1,214 @@ +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" + + "github.com/rwx-cloud/cli/internal/accesstoken" + captaincli "github.com/rwx-cloud/cli/internal/captain/cli" + captainerrors "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/providers" +) + +// testConfig is the internal representation of the captain configuration. +type testConfig struct { + captaincli.ConfigFile + + ProvidersEnv providers.Env + + Secrets struct { + APIToken string + } +} + +type testContextKey string + +var testConfigKey = testContextKey("captainConfig") + +func getTestConfig(cmd *cobra.Command) (testConfig, error) { + val := cmd.Context().Value(testConfigKey) + if val == nil { + return testConfig{}, captainerrors.NewInternalError( + "Tried to fetch config from the command but it wasn't set. This should never happen!") + } + + cfg, ok := val.(testConfig) + if !ok { + return testConfig{}, captainerrors.NewInternalError( + "Tried to fetch config from the command but it was of the wrong type. This should never happen!") + } + + return cfg, nil +} + +func setTestConfigContext(cmd *cobra.Command, cfg testConfig) error { + if _, err := getTestConfig(cmd); err == nil { + return captainerrors.NewInternalError("Tried to set config on the command but it was already set. This should never happen!") + } + + ctx := context.WithValue(cmd.Context(), testConfigKey, cfg) + cmd.SetContext(ctx) + return nil +} + +var captainDirectory = filepath.Join(".rwx", "test") + +const captainConfigName = "config" + +var captainConfigExtensions = []string{"yaml", "yml"} + +func findInParentDir(fileName string) (string, error) { + var match string + var walk func(string, string) error + + walk = func(base, root string) error { + if base == root { + return captainerrors.WithStack(os.ErrNotExist) + } + + match = filepath.Join(base, fileName) + + info, err := os.Stat(match) + if err != nil && !captainerrors.Is(err, os.ErrNotExist) { + return captainerrors.WithStack(err) + } + + if info != nil { + return nil + } + + return walk(filepath.Dir(base), root) + } + + pwd, err := os.Getwd() + if err != nil { + return "", captainerrors.WithStack(err) + } + + volumeName := filepath.VolumeName(pwd) + if volumeName == "" { + volumeName = string(os.PathSeparator) + } + + if err := walk(pwd, volumeName); err != nil { + return "", captainerrors.WithStack(err) + } + + return match, nil +} + +func initTestConfig(cmd *cobra.Command, cliArgs testCliArgs) (cfg testConfig, err error) { + if cliArgs.rootCliArgs.configFilePath == "" { + possibleConfigFilePaths := make([]string, 0, 2) + errs := make([]error, 0, 2) + + for _, extension := range captainConfigExtensions { + configFilePath, err := findInParentDir( + filepath.Join(captainDirectory, fmt.Sprintf("%s.%s", captainConfigName, extension)), + ) + + if err == nil { + possibleConfigFilePaths = append(possibleConfigFilePaths, configFilePath) + } else { + errs = append(errs, err) + } + } + + if len(possibleConfigFilePaths) > 1 { + return cfg, captainerrors.NewConfigurationError( + "Unable to identify configuration file", + fmt.Sprintf( + "rwx test found multiple configuration files in your environment: %s\n", + strings.Join(possibleConfigFilePaths, ", "), + ), + "Please make sure only one config file is present in your environment or explicitly specify "+ + "one using the '--config-file' flag.", + ) + } + + if len(possibleConfigFilePaths) == 0 { + for _, err := range errs { + if err != nil && !captainerrors.Is(err, os.ErrNotExist) { + return cfg, captainerrors.NewConfigurationError( + "Unable to read configuration file", + fmt.Sprintf( + "The following system error occurred while searching for a config file: %s", + err.Error(), + ), + "Please make sure that rwx test has the correct permissions to access the config file, "+ + "or explicitly specify one using the '--config-file' flag.", + ) + } + } + } else { + cliArgs.rootCliArgs.configFilePath = possibleConfigFilePaths[0] + } + } + + if cliArgs.rootCliArgs.configFilePath != "" { + fd, err := os.Open(cliArgs.rootCliArgs.configFilePath) + if err != nil { + if !captainerrors.Is(err, os.ErrNotExist) { + return cfg, captainerrors.Wrap(err, fmt.Sprintf("unable to open config file %q", cliArgs.rootCliArgs.configFilePath)) + } + } else { + defer fd.Close() + decoder := yaml.NewDecoder(fd) + decoder.KnownFields(true) + if err = decoder.Decode(&cfg.ConfigFile); err != nil { + typeError := new(yaml.TypeError) + if captainerrors.As(err, &typeError) { + err = captainerrors.NewConfigurationError( + "Parsing Error", + strings.Join(typeError.Errors, "\n"), + "Please refer to the documentation at https://www.rwx.com/docs/captain/cli-configuration/config-yaml for the"+ + " correct config file syntax.", + ) + } + + return cfg, captainerrors.Wrap(err, "unable to parse config file") + } + } + } + + for name, value := range cfg.Flags { + if err := cmd.Flags().Set(name, fmt.Sprintf("%v", value)); err != nil { + return cfg, captainerrors.Wrap(err, fmt.Sprintf("unable to set flag %q", name)) + } + } + + // Resolve the access token through the file backend so `rwx login` tokens work + accessTokenBackend, err := initAccessTokenBackend() + if err != nil { + return cfg, captainerrors.Wrap(err, "unable to initialize access token backend") + } + token, err := accesstoken.Get(accessTokenBackend, AccessToken) + if err != nil { + return cfg, captainerrors.Wrap(err, "unable to resolve access token") + } + cfg.Secrets.APIToken = token + + if _, ok := cfg.TestSuites[cliArgs.rootCliArgs.suiteID]; !ok { + if cfg.TestSuites == nil { + cfg.TestSuites = make(map[string]captaincli.SuiteConfig) + } + + cfg.TestSuites[cliArgs.rootCliArgs.suiteID] = captaincli.SuiteConfig{} + } + + cfg = bindTestRootFlags(cfg, cliArgs.rootCliArgs) + cfg = bindTestFrameworkFlags(cfg, cliArgs.frameworkParams, cliArgs.rootCliArgs.suiteID) + cfg = bindTestRunFlags(cfg, cliArgs, cmd) + + if err = setTestConfigContext(cmd, cfg); err != nil { + return cfg, captainerrors.WithStack(err) + } + + return cfg, nil +} diff --git a/cmd/rwx/test_init.go b/cmd/rwx/test_init.go new file mode 100644 index 0000000..dc66c15 --- /dev/null +++ b/cmd/rwx/test_init.go @@ -0,0 +1,274 @@ +package main + +import ( + _ "embed" + "encoding/json" + "regexp" + + "github.com/spf13/cobra" + "go.uber.org/zap" + + "github.com/rwx-cloud/cli/internal/captain/backend" + "github.com/rwx-cloud/cli/internal/captain/backend/remote" + captaincli "github.com/rwx-cloud/cli/internal/captain/cli" + captainerrors "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/exec" + captainfs "github.com/rwx-cloud/cli/internal/captain/fs" + "github.com/rwx-cloud/cli/internal/captain/logging" + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/providers" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + "github.com/rwx-cloud/cli/internal/git" +) + +var testMutuallyExclusiveParsers []parsing.Parser = []parsing.Parser{ + parsing.DotNetxUnitParser{}, + parsing.GoGinkgoParser{}, + parsing.GoTestParser{}, + parsing.JavaScriptCypressParser{}, + parsing.JavaScriptJestParser{}, + parsing.JavaScriptVitestParser{}, // Vitest MUST be after Jest as Jest _looks like_ a superset of Vitest + parsing.JavaScriptKarmaParser{}, + parsing.JavaScriptMochaParser{}, + parsing.JavaScriptPlaywrightParser{}, + parsing.PythonPytestParser{}, + parsing.RubyRSpecParser{}, +} + +var testFrameworkParsers map[v1.Framework][]parsing.Parser = map[v1.Framework][]parsing.Parser{ + v1.DotNetxUnitFramework: {parsing.DotNetxUnitParser{}}, + v1.ElixirExUnitFramework: {parsing.ElixirExUnitParser{}}, + v1.GoGinkgoFramework: {parsing.GoGinkgoParser{}}, + v1.GoTestFramework: {parsing.GoTestParser{}}, + v1.JavaScriptCucumberFramework: {parsing.JavaScriptCucumberJSONParser{}}, + v1.JavaScriptCypressFramework: {parsing.JavaScriptCypressParser{}}, + v1.JavaScriptJestFramework: {parsing.JavaScriptJestParser{}}, + v1.JavaScriptKarmaFramework: {parsing.JavaScriptKarmaParser{}}, + v1.JavaScriptMochaFramework: {parsing.JavaScriptMochaParser{}}, + v1.JavaScriptPlaywrightFramework: {parsing.JavaScriptPlaywrightParser{}}, + v1.JavaScriptVitestFramework: {parsing.JavaScriptVitestParser{}}, + v1.JavaScriptBunFramework: {parsing.JUnitTestsuitesParser{}, parsing.JUnitTestsuiteParser{}}, + v1.PHPUnitFramework: {parsing.PHPUnitParser{}}, + v1.PythonPytestFramework: {parsing.PythonPytestParser{}}, + v1.PythonUnitTestFramework: {parsing.PythonUnitTestParser{}}, + v1.RubyCucumberFramework: {parsing.RubyCucumberParser{}}, + v1.RubyMinitestFramework: {parsing.RubyMinitestParser{}}, + v1.RubyRSpecFramework: {parsing.RubyRSpecParser{}}, +} + +var testGenericParsers []parsing.Parser = []parsing.Parser{ + parsing.RWXParser{}, + parsing.JUnitTestsuitesParser{}, + parsing.JUnitTestsuiteParser{}, +} + +var invalidSuiteIDRegexp = regexp.MustCompile(`[^a-zA-Z0-9_-]`) + +type identityRecipe struct { + Language string + Kind string + Recipe struct { + Components []string + Strict bool + } +} + +//go:embed identity_recipes.json +var recipeJSON []byte + +func getTestRecipes() (map[string]v1.TestIdentityRecipe, error) { + var recipeList []identityRecipe + recipes := make(map[string]v1.TestIdentityRecipe) + + if err := json.Unmarshal(recipeJSON, &recipeList); err != nil { + return recipes, captainerrors.NewInternalError("unable to parse identity recipes: %s", err.Error()) + } + + for _, ir := range recipeList { + recipes[v1.CoerceFramework(ir.Language, ir.Kind).String()] = v1.TestIdentityRecipe{ + Components: ir.Recipe.Components, + Strict: ir.Recipe.Strict, + } + } + + return recipes, nil +} + +func initTestService( + cliArgs *testCliArgs, + providerValidator func(providers.Provider) error, +) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + if err := extractTestSuiteID(&cliArgs.rootCliArgs, args); err != nil { + return err + } + + cfg, err := initTestConfig(cmd, *cliArgs) + if err != nil { + return captainerrors.WithDecoration(err) + } + + return initTestServiceWithConfig(cmd, cfg, cliArgs.rootCliArgs.suiteID, providerValidator) + } +} + +func initTestServiceWithConfig( + cmd *cobra.Command, cfg testConfig, suiteID string, providerValidator func(providers.Provider) error, +) error { + err := func() error { + if suiteID == "" { + return captainerrors.NewConfigurationError("Invalid suite-id", "The suite ID is empty.", "") + } + + if invalidSuiteIDRegexp.Match([]byte(suiteID)) { + return captainerrors.NewConfigurationError( + "Invalid suite-id", + "A suite ID can only contain alphanumeric characters, `_` and `-`.", + "Please make sure that the ID doesn't contain any special characters.", + ) + } + + logger := logging.NewProductionLogger() + if cfg.Output.Debug { + logger = logging.NewDebugLogger() + } + + apiClient, err := makeTestAPIClient(cfg, providerValidator, logger, suiteID) + if err != nil { + return captainerrors.Wrap(err, "unable to create API client") + } + + recipes, err := getTestRecipes() + if err != nil { + return captainerrors.Wrap(err, "unable to retrieve test identity recipes") + } + + var parseConfig parsing.Config + if suiteConfig, ok := cfg.TestSuites[suiteID]; ok { + parseConfig = parsing.Config{ + ProvidedFrameworkKind: suiteConfig.Results.Framework, + ProvidedFrameworkLanguage: suiteConfig.Results.Language, + FailOnDuplicateTestID: suiteConfig.FailOnDuplicateTestID, + MutuallyExclusiveParsers: testMutuallyExclusiveParsers, + FrameworkParsers: testFrameworkParsers, + GenericParsers: testGenericParsers, + Logger: logger, + IdentityRecipes: recipes, + } + } + + if err := parseConfig.Validate(); err != nil { + return captainerrors.Wrap(err, "invalid parser config") + } + + captain := captaincli.Service{ + API: apiClient, + Log: logger, + FileSystem: captainfs.Local{}, + TaskRunner: exec.Local{}, + ParseConfig: parseConfig, + } + + if err := captaincli.SetService(cmd, captain); err != nil { + return captainerrors.WithStack(err) + } + + return nil + }() + if err != nil { + return captainerrors.WithDecoration(err) + } + return nil +} + +func unsafeInitTestParsingOnly(cliArgs *testCliArgs) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + err := func() error { + cliArgs.rootCliArgs.positionalArgs = args + if cliArgs.rootCliArgs.suiteID == "" { + cliArgs.rootCliArgs.suiteID = "placeholder" + } + + cfg, err := initTestConfig(cmd, *cliArgs) + if err != nil { + return captainerrors.WithStack(err) + } + + logger := logging.NewProductionLogger() + if cfg.Output.Debug { + logger = logging.NewDebugLogger() + } + + recipes, err := getTestRecipes() + if err != nil { + return captainerrors.Wrap(err, "unable to retrieve test identity recipes") + } + + var parseConfig parsing.Config + if suiteConfig, ok := cfg.TestSuites[cliArgs.rootCliArgs.suiteID]; ok { + parseConfig = parsing.Config{ + ProvidedFrameworkKind: suiteConfig.Results.Framework, + ProvidedFrameworkLanguage: suiteConfig.Results.Language, + FailOnDuplicateTestID: suiteConfig.FailOnDuplicateTestID, + MutuallyExclusiveParsers: testMutuallyExclusiveParsers, + FrameworkParsers: testFrameworkParsers, + GenericParsers: testGenericParsers, + Logger: logger, + IdentityRecipes: recipes, + } + } + + if err := parseConfig.Validate(); err != nil { + return captainerrors.Wrap(err, "invalid parser config") + } + + captain := captaincli.Service{ + Log: logger, + FileSystem: captainfs.Local{}, + ParseConfig: parseConfig, + } + if err := captaincli.SetService(cmd, captain); err != nil { + return captainerrors.WithStack(err) + } + + return nil + }() + if err != nil { + return captainerrors.WithDecoration(err) + } + return nil + } +} + +func makeTestAPIClient( + cfg testConfig, providerValidator func(providers.Provider) error, logger *zap.SugaredLogger, suiteID string, +) (backend.Client, error) { + gitClient := &git.Client{Binary: "git", Dir: "."} + cfg.ProvidersEnv.Generic.PopulateFromGit(gitClient) + + if cfg.Secrets.APIToken == "" { + return nil, captainerrors.NewConfigurationError( + "Missing access token", + "rwx test requires an RWX access token to communicate with RWX Cloud.", + "Set the RWX_ACCESS_TOKEN environment variable or run 'rwx login' to authenticate.", + ) + } + + provider, err := cfg.ProvidersEnv.MakeProvider() + if err != nil { + return nil, captainerrors.Wrap(err, "failed to construct provider") + } + if err = providerValidator(provider); err != nil { + return nil, err + } + + client, err := remote.NewClient(remote.ClientConfig{ + Debug: cfg.Output.Debug, + Host: cfg.Cloud.APIHost, + Insecure: cfg.Cloud.Insecure, + Log: logger, + Token: cfg.Secrets.APIToken, + Provider: provider, + }) + return client, captainerrors.WithStack(err) +} diff --git a/cmd/rwx/test_merge.go b/cmd/rwx/test_merge.go new file mode 100644 index 0000000..d34db1a --- /dev/null +++ b/cmd/rwx/test_merge.go @@ -0,0 +1,93 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + + captaincli "github.com/rwx-cloud/cli/internal/captain/cli" + captainerrors "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/reporting" +) + +func configureTestMergeCmd(parentCmd *cobra.Command, cliArgs *testCliArgs) { + mergeCmd := &cobra.Command{ + Use: "merge results-glob [results-globs] ", + Short: "Merge test results files", + Long: "'rwx test results merge' takes test results files produced by partitioned " + + "executions and merges them into a single set of results.", + Example: ` rwx test results merge tmp/results/*.json`, + Args: cobra.MinimumNArgs(1), + PreRunE: unsafeInitTestParsingOnly(cliArgs), + RunE: func(cmd *cobra.Command, _ []string) error { + err := func() error { + captain, err := captaincli.GetService(cmd) + if err != nil { + return captainerrors.WithStack(err) + } + + reporterFuncs := make(map[string]captaincli.Reporter) + for _, r := range cliArgs.reporters { + name, path, _ := strings.Cut(r, "=") + + switch name { + case "rwx-v1-json": + reporterFuncs[path] = reporting.WriteJSONSummary + case "junit-xml": + reporterFuncs[path] = reporting.WriteJUnitSummary + case "markdown-summary": + reporterFuncs[path] = reporting.WriteMarkdownSummary + case "github-step-summary": + stepSummaryPath := os.Getenv("GITHUB_STEP_SUMMARY") + if stepSummaryPath == "" { + captain.Log.Debug( + "Skipping configuration of the 'github-step-summary' reporter " + + "(the 'GITHUB_STEP_SUMMARY' environment variable is not set).", + ) + continue + } + + reporterFuncs[stepSummaryPath] = reporting.WriteMarkdownSummary + default: + return captainerrors.NewConfigurationError( + fmt.Sprintf("Unknown reporter %q", name), + "Available reporters are 'rwx-v1-json', 'junit-xml', 'markdown-summary', and 'github-step-summary'.", + "", + ) + } + } + + mergeConfig := captaincli.MergeConfig{ + ResultsGlobs: cliArgs.rootCliArgs.positionalArgs, + PrintSummary: cliArgs.printSummary, + Reporters: reporterFuncs, + } + + err = captain.Merge(cmd.Context(), mergeConfig) + if _, ok := captainerrors.AsConfigurationError(err); !ok { + cmd.SilenceUsage = true + } + + return captainerrors.WithStack(err) + }() + if err != nil { + return captainerrors.WithDecoration(err) + } + + return nil + }, + } + + mergeCmd.Flags().BoolVar(&cliArgs.printSummary, "print-summary", false, "prints a summary of all tests to the console") + + mergeCmd.Flags().StringArrayVar(&cliArgs.reporters, "reporter", []string{}, + "one or more `type=output_path` pairs to enable different reporting options.\n"+ + "Available reporters are 'rwx-v1-json', 'junit-xml', 'markdown-summary', and 'github-step-summary'.", + ) + + addTestFrameworkFlags(mergeCmd, &cliArgs.frameworkParams) + + parentCmd.AddCommand(mergeCmd) +} diff --git a/cmd/rwx/test_partition.go b/cmd/rwx/test_partition.go new file mode 100644 index 0000000..6a1e6b5 --- /dev/null +++ b/cmd/rwx/test_partition.go @@ -0,0 +1,130 @@ +package main + +import ( + "github.com/spf13/cobra" + + captaincli "github.com/rwx-cloud/cli/internal/captain/cli" + "github.com/rwx-cloud/cli/internal/captain/config" + captainerrors "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/providers" +) + +func configureTestPartitionCmd(parentCmd *cobra.Command, cliArgs *testCliArgs) { + var pNodes config.PartitionNodes + var delimiter string + var roundRobin bool + var trimPrefix string + + partitionCmd := &cobra.Command{ + Use: "partition [--help] [--config-file=] [--delimiter=] [--sha=] --suite-id= --index= " + + "--total= ", + Short: "Partition a test suite using historical file timings", + Long: "'rwx test partition' can be used to split up your test suite by test file, leveraging test file timings " + + "recorded by rwx test.", + Example: "" + + " bundle exec rspec $(rwx test partition your-project-rspec --index 0 --total 2 spec/**/*_spec.rb)\n" + + " bundle exec rspec $(rwx test partition your-project-rspec --index 1 --total 2 spec/**/*_spec.rb)", + Args: cobra.MinimumNArgs(1), + DisableFlagsInUseLine: true, + PreRunE: func(cmd *cobra.Command, args []string) error { + err := func() error { + if err := extractTestSuiteID(&cliArgs.rootCliArgs, args); err != nil { + return err + } + + cfg, err := initTestConfig(cmd, *cliArgs) + if err != nil { + return err + } + + provider, err := cfg.ProvidersEnv.MakeProvider() + if err != nil { + return captainerrors.Wrap(err, "failed to construct provider") + } + + if pNodes.Index < 0 { + if provider.PartitionNodes.Index < 0 { + return captainerrors.NewConfigurationError( + "Partition index invalid.", + "Partition index must be 0 or greater.", + "You can set the partition index by using the --index flag or the RWX_TEST_PARTITION_INDEX environment variable.", + ) + } + pNodes.Index = provider.PartitionNodes.Index + } + + if pNodes.Total < 0 { + if provider.PartitionNodes.Total < 1 { + return captainerrors.NewConfigurationError( + "Partition total invalid.", + "Partition total must be 1 or greater.", + "You can set the partition total by using the --total flag or the RWX_TEST_PARTITION_TOTAL environment variable.", + ) + } + pNodes.Total = provider.PartitionNodes.Total + } + + return initTestServiceWithConfig(cmd, cfg, cliArgs.rootCliArgs.suiteID, func(p providers.Provider) error { + if p.CommitSha == "" { + return captainerrors.NewConfigurationError( + "Missing commit SHA", + "rwx test requires a commit SHA in order to track test runs correctly.", + "You can specify the SHA by using the --sha flag or the RWX_TEST_SHA environment variable", + ) + } + return nil + }) + }() + if err != nil { + return captainerrors.WithDecoration(err) + } + return nil + }, + + RunE: func(cmd *cobra.Command, _ []string) error { + err := func() error { + args := cliArgs.rootCliArgs.positionalArgs + captain, err := captaincli.GetService(cmd) + if err != nil { + return captainerrors.WithStack(err) + } + err = captain.Partition(cmd.Context(), captaincli.PartitionConfig{ + SuiteID: cliArgs.rootCliArgs.suiteID, + TestFilePaths: args, + PartitionNodes: pNodes, + Delimiter: delimiter, + RoundRobin: roundRobin, + TrimPrefix: trimPrefix, + }) + return captainerrors.WithStack(err) + }() + if err != nil { + return captainerrors.WithDecoration(err) + } + return nil + }, + } + + partitionCmd.Flags().IntVar(&pNodes.Index, "index", -1, "the 0-indexed index of a particular partition") + partitionCmd.Flags().IntVar(&pNodes.Total, "total", -1, "the total number of partitions") + addTestShaFlag(partitionCmd, &cliArgs.genericProvider.Sha) + + defaultDelimiter := getEnvWithFallback("RWX_TEST_DELIMITER", "CAPTAIN_DELIMITER") + if defaultDelimiter == "" { + defaultDelimiter = " " + } + + partitionCmd.Flags().StringVar(&delimiter, "delimiter", defaultDelimiter, + "the delimiter used to separate partitioned files.\nIt can also be set using the env var RWX_TEST_DELIMITER.") + + partitionCmd.Flags().BoolVar(&roundRobin, "round-robin", false, + "Whether to naively round robin tests across partitions. When false, historical test timing data will be used to"+ + " evenly balance the partitions.", + ) + + partitionCmd.Flags().StringVar(&trimPrefix, "trim-prefix", "", + "A prefix to trim from the beginning of local test file paths when comparing them to historical timing data.", + ) + + parentCmd.AddCommand(partitionCmd) +} diff --git a/cmd/rwx/test_results.go b/cmd/rwx/test_results.go new file mode 100644 index 0000000..e014e9a --- /dev/null +++ b/cmd/rwx/test_results.go @@ -0,0 +1,51 @@ +package main + +import ( + "github.com/spf13/cobra" + + captaincli "github.com/rwx-cloud/cli/internal/captain/cli" + captainerrors "github.com/rwx-cloud/cli/internal/captain/errors" +) + +func configureTestResultsCmd(parentCmd *cobra.Command, cliArgs *testCliArgs) { + resultsParseCmd := &cobra.Command{ + Use: "parse [flags] ", + Short: "Parse test results files into RWX v1 JSON", + Long: "'rwx test results parse' will parse test results files and output RWX v1 JSON.", + Example: ` rwx test results parse rspec.json`, + Args: cobra.MinimumNArgs(1), + PreRunE: unsafeInitTestParsingOnly(cliArgs), + RunE: func(cmd *cobra.Command, _ []string) error { + err := func() error { + artifacts := cliArgs.rootCliArgs.positionalArgs + + captain, err := captaincli.GetService(cmd) + if err != nil { + return captainerrors.WithStack(err) + } + + err = captain.Parse(cmd.Context(), artifacts) + if _, ok := captainerrors.AsConfigurationError(err); !ok { + cmd.SilenceUsage = true + } + + return captainerrors.WithStack(err) + }() + if err != nil { + return captainerrors.WithDecoration(err) + } + return nil + }, + } + + addTestFrameworkFlags(resultsParseCmd, &cliArgs.frameworkParams) + + resultsCmd := &cobra.Command{ + Use: "results", + Short: "Manage test results", + } + + resultsCmd.AddCommand(resultsParseCmd) + configureTestMergeCmd(resultsCmd, cliArgs) + parentCmd.AddCommand(resultsCmd) +} diff --git a/go.mod b/go.mod index 7174ce5..fa8f599 100644 --- a/go.mod +++ b/go.mod @@ -2,25 +2,36 @@ module github.com/rwx-cloud/cli go 1.24.0 -toolchain go1.24.1 - require ( al.essio.dev/pkg/shellescape v1.6.0 github.com/Masterminds/semver/v3 v3.4.0 + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d + github.com/bmatcuk/doublestar/v4 v4.10.0 + github.com/bradleyjkemp/cupaloy v2.3.0+incompatible github.com/briandowns/spinner v1.23.2 github.com/distribution/reference v0.6.0 github.com/docker/cli v28.5.2+incompatible github.com/docker/docker v28.5.2+incompatible github.com/goccy/go-yaml v1.19.2 + github.com/google/uuid v1.6.0 github.com/kopoli/go-terminal-size v0.0.0-20170219200355-5c97524c8b54 github.com/manifoldco/promptui v0.9.0 + github.com/mattn/go-shellwords v1.0.12 + github.com/mileusna/useragent v1.3.5 + github.com/mitchellh/go-wordwrap v1.0.1 github.com/moby/moby v28.5.2+incompatible github.com/modelcontextprotocol/go-sdk v1.3.0 + github.com/onsi/ginkgo/v2 v2.28.1 + github.com/onsi/gomega v1.39.0 github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 + go.uber.org/zap v1.27.1 golang.org/x/crypto v0.48.0 + golang.org/x/sync v0.19.0 golang.org/x/term v0.40.0 + golang.org/x/text v0.34.0 + gopkg.in/yaml.v3 v3.0.1 ) require github.com/mattn/go-isatty v0.0.20 // indirect @@ -44,8 +55,10 @@ require ( github.com/fvbommel/sortorder v1.1.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/mattn/go-colorable v0.1.2 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect @@ -72,15 +85,17 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect + go.uber.org/multierr v1.10.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/mod v0.32.0 // indirect golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.13.0 // indirect + golang.org/x/tools v0.41.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/grpc v1.75.0 // indirect google.golang.org/protobuf v1.36.8 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.2 // indirect ) diff --git a/go.sum b/go.sum index 14487b1..fd6abf5 100644 --- a/go.sum +++ b/go.sum @@ -8,11 +8,17 @@ github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr github.com/Microsoft/go-winio v0.4.21 h1:+6mVbXh4wPzUrl1COX9A+ZCvEpYsOBZ6/+kwDnvLyro= github.com/Microsoft/go-winio v0.4.21/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f h1:L/FlB1krOjojJSmUaiAiOMiIdRWylhc9QcHg0vHBuzA= github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bradleyjkemp/cupaloy v2.3.0+incompatible h1:UafIjBvWQmS9i/xRg+CamMrnLTKNzo+bdmT/oH34c2Y= +github.com/bradleyjkemp/cupaloy v2.3.0+incompatible/go.mod h1:Au1Xw1sgaJ5iSFktEhYsS0dbQiS1B0/XMXl+42y9Ilk= github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w= github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM= github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= @@ -76,12 +82,20 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -97,6 +111,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -113,6 +129,8 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -129,6 +147,8 @@ github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTRe github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -136,11 +156,19 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/miekg/pkcs11 v1.0.2 h1:CIBkOawOtzJNE0B+EpRiUBzuVW7JEQAwdwhSS6YhIeg= github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mileusna/useragent v1.3.5 h1:SJM5NzBmh/hO+4LGeATKpaEX9+b4vcGg2qXGLiNGDws= +github.com/mileusna/useragent v1.3.5/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= @@ -159,8 +187,12 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= +github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -212,6 +244,14 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c= github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -240,6 +280,11 @@ go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOV go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -247,6 +292,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= @@ -254,6 +301,8 @@ golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/internal/captain/backend/remote/client.go b/internal/captain/backend/remote/client.go new file mode 100644 index 0000000..b50efa6 --- /dev/null +++ b/internal/captain/backend/remote/client.go @@ -0,0 +1,311 @@ +// Package remote holds the main API client for Captain & supporting types. Overall, this should be a fairly transparent +// package, mapping HTTP calls to Go methods - however some endpoints are a bit more complex & require multiple calls +// back-and forth. +package remote + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httputil" + "strings" + + "github.com/rwx-cloud/cli/cmd/rwx/config" + "github.com/rwx-cloud/cli/internal/captain/backend" + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/testing" +) + +// Client is the main client for the Captain API. +type Client struct { + ClientConfig + RoundTrip func(*http.Request) (*http.Response, error) +} + +// NewClient is the preferred constructor for the API client. It makes sure that the configuration is valid & necessary +// defaults are applied. +func NewClient(cfg ClientConfig) (Client, error) { + cfg = cfg.WithDefaults() + + if err := cfg.Validate(); err != nil { + return Client{}, err + } + + client := &http.Client{} + + roundTrip := func(req *http.Request) (*http.Response, error) { + // This is a bit hacky. In theory, this roundtripper should solely be used for accessing Captain's own API. + // However, it turns out that the API expects us to upload test results to S3 directly as well. + // We re-use the same roundtripper here primarily to make mocking easier. Alternatives would be to + // a) have two different roundtrippers (or one for each configured host) + // b) have the API client instantiate another API client + // c) move all of this sequental logic out of the API layer + // None of these options are great - having this special case for the S3 upload seems the least bad (given + // that there is only a single occurrence) + if !strings.HasSuffix(req.URL.Host, "amazonaws.com") { + req.URL.Scheme = "https" + if cfg.Insecure { + req.URL.Scheme = "http" + } + + req.URL.Host = cfg.Host + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", cfg.Token)) + req.Header.Set("User-Agent", fmt.Sprintf("rwx-cli/%s", config.Version)) + } + + if cfg.Debug { + hasBody := req.Body != nil + dump, _ := httputil.DumpRequest(req, hasBody) + sanitizedDump := bearerTokenRegexp.ReplaceAll(dump, []byte("")) + cfg.Log.Debugf("Executing following HTTP request:\n\n%s\n", sanitizedDump) + } + + resp, err := client.Do(req) + if err != nil { + return resp, errors.NewSystemError("unable to perform HTTP request to %q: %s", req.URL, err) + } + + if cfg.Debug { + dump, _ := httputil.DumpResponse(resp, true) + sanitizedDump := setCookieHeaderRegexp.ReplaceAll(dump, []byte("Set-Cookie: ")) + cfg.Log.Debugf("Received following response:\n\n%s\n", sanitizedDump) + } + + return resp, nil + } + + return Client{cfg, roundTrip}, nil +} + +func (c Client) GetTestTimingManifest( + ctx context.Context, + testSuiteIdentifier string, +) ([]testing.TestFileTiming, error) { + endpoint := hostEndpointCompat(c, "/api/test_suites/timing_manifest") + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, errors.NewInternalError("unable to construct HTTP request: %s", err) + } + + queryValues := req.URL.Query() + queryValues.Add("test_suite_identifier", testSuiteIdentifier) + if c.Provider.TimingManifestKey != "" { + queryValues.Add("commit_sha", c.Provider.TimingManifestKey) + } else { + queryValues.Add("commit_sha", c.Provider.CommitSha) + } + req.URL.RawQuery = queryValues.Encode() + + resp, err := c.RoundTrip(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return nil, errors.NewInternalError( + "API backend encountered an error. Endpoint was %q, Status Code %d", + endpoint, + resp.StatusCode, + ) + } + + respBody := struct { + FileTimings []testing.TestFileTiming `json:"file_timings"` + }{} + + if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil { + return nil, errors.NewInternalError( + "unable to parse the response body. Endpoint was %q, Content-Type %q. Original Error: %s", + endpoint, + resp.Header.Get(headerContentType), + err, + ) + } + + return respBody.FileTimings, nil +} + +func (c Client) logError(err error) error { + c.Log.Errorf(err.Error()) + return err +} + +func (c Client) postJSON(ctx context.Context, endpoint string, body any) (*http.Response, error) { + encodedBody, err := json.Marshal(body) + if err != nil { + return nil, errors.NewInternalError("unable to construct JSON object for request: %s", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(encodedBody)) + if err != nil { + return nil, errors.NewInternalError("unable to construct HTTP request: %s", err) + } + + req.Header.Set(headerContentType, contentTypeJSON) + + resp, err := c.RoundTrip(req) + if err != nil { + return nil, err + } + + return resp, nil +} + +func (c Client) putJSON(ctx context.Context, endpoint string, body any) (*http.Response, error) { + encodedBody, err := json.Marshal(body) + if err != nil { + return nil, errors.NewInternalError("unable to construct JSON object for request: %s", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, endpoint, bytes.NewBuffer(encodedBody)) + if err != nil { + return nil, errors.NewInternalError("unable to construct HTTP request: %s", err) + } + + req.Header.Set(headerContentType, contentTypeJSON) + + resp, err := c.RoundTrip(req) + if err != nil { + return nil, err + } + + return resp, nil +} + +// GetRunConfiguration returns the runtime configuration for the run command (e.g. quarantined and flaky tests) +func (c Client) GetRunConfiguration( + ctx context.Context, + testSuiteIdentifier string, +) (backend.RunConfiguration, error) { + endpoint := hostEndpointCompat(c, "/api/test_suites/run_configuration") + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return backend.RunConfiguration{}, errors.NewInternalError("unable to construct HTTP request: %s", err) + } + + queryValues := req.URL.Query() + queryValues.Add("test_suite_identifier", testSuiteIdentifier) + req.URL.RawQuery = queryValues.Encode() + + resp, err := c.RoundTrip(req) + if err != nil { + return backend.RunConfiguration{}, err + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return backend.RunConfiguration{}, errors.NewInternalError( + "API backend encountered an error. Endpoint was %q, Status Code %d", + endpoint, + resp.StatusCode, + ) + } + + runConfiguration := backend.RunConfiguration{} + if err := json.NewDecoder(resp.Body).Decode(&runConfiguration); err != nil { + return backend.RunConfiguration{}, errors.NewInternalError( + "unable to parse the response body. Endpoint was %q, Content-Type %q. Original Error: %s", + endpoint, + resp.Header.Get(headerContentType), + err, + ) + } + + return runConfiguration, nil +} + +// GetQuarantinedTests returns only the list of quarantined tests +func (c Client) GetQuarantinedTests( + ctx context.Context, + testSuiteIdentifier string, +) ([]backend.Test, error) { + endpoint := hostEndpointCompat(c, "/api/test_suites/quarantined_tests") + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, errors.NewInternalError("unable to construct HTTP request: %s", err) + } + + queryValues := req.URL.Query() + queryValues.Add("test_suite_identifier", testSuiteIdentifier) + req.URL.RawQuery = queryValues.Encode() + + resp, err := c.RoundTrip(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return nil, errors.NewInternalError( + "API backend encountered an error. Endpoint was %q, Status Code %d", + endpoint, + resp.StatusCode, + ) + } + + respBody := struct { + QuarantinedTests []backend.Test `json:"quarantined_tests"` + }{} + + if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil { + return nil, errors.NewInternalError( + "unable to parse the response body. Endpoint was %q, Content-Type %q. Original Error: %s", + endpoint, + resp.Header.Get(headerContentType), + err, + ) + } + + return respBody.QuarantinedTests, nil +} + +func (c Client) GetIdentityRecipes(ctx context.Context) ([]byte, error) { + endpoint := hostEndpointCompat(c, "/api/recipes") + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, errors.NewInternalError("unable to construct HTTP request: %s", err) + } + + resp, err := c.RoundTrip(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return nil, errors.NewInternalError( + "API backend encountered an error. Endpoint was %q, Status Code %d", + endpoint, + resp.StatusCode, + ) + } + + buffer := bytes.NewBuffer(make([]byte, 0)) + _, err = buffer.ReadFrom(resp.Body) + if err != nil { + return nil, errors.NewInternalError( + "Unable to read HTTP response body from API. Endpoint was %q, Status Code %d", + endpoint, + resp.StatusCode, + ) + } + + return buffer.Bytes(), nil +} + +// TODO(TS): Remove this once we're no longer testing against versions that use captain.build +func hostEndpointCompat(c Client, endpoint string) string { + remoteHost := c.Host + + if !strings.Contains(remoteHost, "cloud") { + return endpoint + } + return "/captain" + endpoint +} diff --git a/internal/captain/backend/remote/config.go b/internal/captain/backend/remote/config.go new file mode 100644 index 0000000..3081817 --- /dev/null +++ b/internal/captain/backend/remote/config.go @@ -0,0 +1,51 @@ +package remote + +import ( + "github.com/google/uuid" + "go.uber.org/zap" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/providers" +) + +// ClientConfig is the configuration object for the Captain API client +type ClientConfig struct { + Debug bool + Host string + Insecure bool + Log *zap.SugaredLogger + Token string + Provider providers.Provider + NewUUID func() (uuid.UUID, error) +} + +// Validate checks the configuration for errors +func (cfg ClientConfig) Validate() error { + if cfg.Log == nil { + return errors.NewInternalError("missing logger") + } + + if cfg.Token == "" { + return errors.NewConfigurationError( + "Missing API token", + "In order to use the CLI in conjunction with Captain Cloud, please supply an API token.", + "The token can be set by using the RWX_ACCESS_TOKEN environment variable. If you don't have a token yet "+ + "you can create one under \"Organization Settings\" at https://cloud.rwx.com/.", + ) + } + + return nil +} + +// WithDefaults returns a copy of the configuration with defaults applied where necessary. +func (cfg ClientConfig) WithDefaults() ClientConfig { + if cfg.Host == "" { + cfg.Host = defaultHost + } + + if cfg.NewUUID == nil { + cfg.NewUUID = uuid.NewRandom + } + + return cfg +} diff --git a/internal/captain/backend/remote/defaults.go b/internal/captain/backend/remote/defaults.go new file mode 100644 index 0000000..8214af8 --- /dev/null +++ b/internal/captain/backend/remote/defaults.go @@ -0,0 +1,15 @@ +package remote + +import "regexp" + +const ( + defaultHost = "cloud.rwx.com" + + contentTypeJSON = "application/json" + headerContentType = "Content-Type" +) + +var ( + bearerTokenRegexp = regexp.MustCompile(`Bearer.*`) + setCookieHeaderRegexp = regexp.MustCompile(`Set-Cookie:.*`) +) diff --git a/internal/captain/backend/remote/get_quarantined_tests_test.go b/internal/captain/backend/remote/get_quarantined_tests_test.go new file mode 100644 index 0000000..0e22c3a --- /dev/null +++ b/internal/captain/backend/remote/get_quarantined_tests_test.go @@ -0,0 +1,181 @@ +package remote_test + +import ( + "context" + "io" + "net/http" + "strings" + + "go.uber.org/zap" + + "github.com/rwx-cloud/cli/internal/captain/backend/remote" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GetQuarantinedTests", func() { + var ( + apiClient remote.Client + mockRoundTripper func(*http.Request) (*http.Response, error) + host string + ) + + JustBeforeEach(func() { + apiClientConfig := remote.ClientConfig{Log: zap.NewNop().Sugar(), Host: host} + apiClient = remote.Client{ClientConfig: apiClientConfig, RoundTrip: mockRoundTripper} + }) + + Context("when the response is successful against captain.build", func() { + BeforeEach(func() { + host = "captain.build" + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + Expect(req.Method).To(Equal(http.MethodGet)) + Expect(req.URL.Path).To(HaveSuffix("/api/test_suites/quarantined_tests")) + Expect(req.URL.Query().Has("test_suite_identifier")).To(BeTrue()) + + resp.Body = io.NopCloser(strings.NewReader(` + { + "quarantined_tests": [ + { + "composite_identifier": "q-1", + "identity_components": ["component1", "component2"], + "strict_identity": true + }, + { + "composite_identifier": "q-2", + "identity_components": ["component3"], + "strict_identity": false + } + ] + } + `)) + resp.StatusCode = 200 + + return &resp, nil + } + }) + + It("returns the quarantined tests", func() { + quarantinedTests, err := apiClient.GetQuarantinedTests(context.Background(), "test-suite-id") + Expect(err).NotTo(HaveOccurred()) + Expect(quarantinedTests).To(HaveLen(2)) + Expect(quarantinedTests[0].CompositeIdentifier).To(Equal("q-1")) + Expect(quarantinedTests[0].IdentityComponents).To(Equal([]string{"component1", "component2"})) + Expect(quarantinedTests[0].StrictIdentity).To(BeTrue()) + Expect(quarantinedTests[1].CompositeIdentifier).To(Equal("q-2")) + Expect(quarantinedTests[1].IdentityComponents).To(Equal([]string{"component3"})) + Expect(quarantinedTests[1].StrictIdentity).To(BeFalse()) + }) + }) + + Context("when the response is successful against cloud.rwx.com", func() { + BeforeEach(func() { + host = "cloud.rwx.com" + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + Expect(req.Method).To(Equal(http.MethodGet)) + Expect(req.URL.Path).To(HaveSuffix("/captain/api/test_suites/quarantined_tests")) + Expect(req.URL.Query().Has("test_suite_identifier")).To(BeTrue()) + + resp.Body = io.NopCloser(strings.NewReader(` + { + "quarantined_tests": [ + { + "composite_identifier": "q-1", + "identity_components": ["component1", "component2"], + "strict_identity": true + } + ] + } + `)) + resp.StatusCode = 200 + + return &resp, nil + } + }) + + It("returns the quarantined tests", func() { + quarantinedTests, err := apiClient.GetQuarantinedTests(context.Background(), "test-suite-id") + Expect(err).NotTo(HaveOccurred()) + Expect(quarantinedTests).To(HaveLen(1)) + Expect(quarantinedTests[0].CompositeIdentifier).To(Equal("q-1")) + }) + }) + + Context("when the response is empty", func() { + BeforeEach(func() { + host = "cloud.rwx.com" + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + Expect(req.Method).To(Equal(http.MethodGet)) + Expect(req.URL.Path).To(HaveSuffix("/captain/api/test_suites/quarantined_tests")) + Expect(req.URL.Query().Has("test_suite_identifier")).To(BeTrue()) + + resp.Body = io.NopCloser(strings.NewReader(`{ "quarantined_tests": [] }`)) + resp.StatusCode = 200 + + return &resp, nil + } + }) + + It("returns an empty list", func() { + quarantinedTests, err := apiClient.GetQuarantinedTests(context.Background(), "test-suite-id") + Expect(err).NotTo(HaveOccurred()) + Expect(quarantinedTests).To(BeEmpty()) + }) + }) + + Context("when the response is not successful", func() { + BeforeEach(func() { + host = "cloud.rwx.com" + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + Expect(req.Method).To(Equal(http.MethodGet)) + Expect(req.URL.Path).To(HaveSuffix("quarantined_tests")) + Expect(req.URL.Query().Has("test_suite_identifier")).To(BeTrue()) + + resp.StatusCode = 400 + resp.Body = io.NopCloser(strings.NewReader("")) + + return &resp, nil + } + }) + + It("returns an error", func() { + quarantinedTests, err := apiClient.GetQuarantinedTests(context.Background(), "test-suite-id") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("API backend encountered an error")) + Expect(quarantinedTests).To(BeNil()) + }) + }) + + Context("when the response body is invalid JSON", func() { + BeforeEach(func() { + host = "cloud.rwx.com" + mockRoundTripper = func(_ *http.Request) (*http.Response, error) { + var resp http.Response + + resp.Body = io.NopCloser(strings.NewReader(`invalid json`)) + resp.StatusCode = 200 + resp.Header = http.Header{ + "Content-Type": []string{"application/json"}, + } + + return &resp, nil + } + }) + + It("returns an error", func() { + quarantinedTests, err := apiClient.GetQuarantinedTests(context.Background(), "test-suite-id") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("unable to parse the response body")) + Expect(quarantinedTests).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/backend/remote/get_run_configuration_test.go b/internal/captain/backend/remote/get_run_configuration_test.go new file mode 100644 index 0000000..e86b5f1 --- /dev/null +++ b/internal/captain/backend/remote/get_run_configuration_test.go @@ -0,0 +1,119 @@ +package remote_test + +import ( + "context" + "io" + "net/http" + "strings" + + "go.uber.org/zap" + + "github.com/rwx-cloud/cli/internal/captain/backend/remote" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GetRunConfiguration", func() { + var ( + apiClient remote.Client + mockRoundTripper func(*http.Request) (*http.Response, error) + host string + ) + + JustBeforeEach(func() { + apiClientConfig := remote.ClientConfig{Log: zap.NewNop().Sugar(), Host: host} + apiClient = remote.Client{ClientConfig: apiClientConfig, RoundTrip: mockRoundTripper} + }) + + Context("when the response is successful against captain.build", func() { + BeforeEach(func() { + host = "captain.build" + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + Expect(req.Method).To(Equal(http.MethodGet)) + Expect(req.URL.Path).To(HaveSuffix("/api/test_suites/run_configuration")) + Expect(req.URL.Query().Has("test_suite_identifier")).To(BeTrue()) + + resp.Body = io.NopCloser(strings.NewReader(` + { + "generated_at": "some-time", + "quarantined_tests": [{"composite_identifier": "q-1"}], + "flaky_tests": [{"composite_identifier": "f-1"}] + } + `)) + resp.StatusCode = 200 + + return &resp, nil + } + }) + + It("returns the run configuration", func() { + runConfiguration, err := apiClient.GetRunConfiguration(context.Background(), "test-suite-id") + Expect(err).NotTo(HaveOccurred()) + Expect(runConfiguration.QuarantinedTests).To(HaveLen(1)) + Expect(runConfiguration.QuarantinedTests[0].CompositeIdentifier).To(Equal("q-1")) + Expect(runConfiguration.FlakyTests).To(HaveLen(1)) + Expect(runConfiguration.FlakyTests[0].CompositeIdentifier).To(Equal("f-1")) + }) + }) + + Context("when the response is successful", func() { + BeforeEach(func() { + host = "cloud.rwx.com" + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + Expect(req.Method).To(Equal(http.MethodGet)) + Expect(req.URL.Path).To(HaveSuffix("/captain/api/test_suites/run_configuration")) + Expect(req.URL.Query().Has("test_suite_identifier")).To(BeTrue()) + + resp.Body = io.NopCloser(strings.NewReader(` + { + "generated_at": "some-time", + "quarantined_tests": [{"composite_identifier": "q-1"}], + "flaky_tests": [{"composite_identifier": "f-1"}] + } + `)) + resp.StatusCode = 200 + + return &resp, nil + } + }) + + It("returns the run configuration", func() { + runConfiguration, err := apiClient.GetRunConfiguration(context.Background(), "test-suite-id") + Expect(err).NotTo(HaveOccurred()) + Expect(runConfiguration.QuarantinedTests).To(HaveLen(1)) + Expect(runConfiguration.QuarantinedTests[0].CompositeIdentifier).To(Equal("q-1")) + Expect(runConfiguration.FlakyTests).To(HaveLen(1)) + Expect(runConfiguration.FlakyTests[0].CompositeIdentifier).To(Equal("f-1")) + }) + }) + + Context("when the response is not successful", func() { + BeforeEach(func() { + host = "cloud.rwx.com" + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + Expect(req.Method).To(Equal(http.MethodGet)) + Expect(req.URL.Path).To(HaveSuffix("run_configuration")) + Expect(req.URL.Query().Has("test_suite_identifier")).To(BeTrue()) + + resp.StatusCode = 400 + resp.Body = io.NopCloser(strings.NewReader("")) + + return &resp, nil + } + }) + + It("returns an error", func() { + runConfiguration, err := apiClient.GetRunConfiguration(context.Background(), "test-suite-id") + Expect(err).To(HaveOccurred()) + Expect(runConfiguration.QuarantinedTests).To(BeNil()) + Expect(runConfiguration.FlakyTests).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/backend/remote/get_test_timing_manifest_test.go b/internal/captain/backend/remote/get_test_timing_manifest_test.go new file mode 100644 index 0000000..d97ff60 --- /dev/null +++ b/internal/captain/backend/remote/get_test_timing_manifest_test.go @@ -0,0 +1,88 @@ +package remote_test + +import ( + "context" + "io" + "net/http" + "strings" + + "go.uber.org/zap" + + "github.com/rwx-cloud/cli/internal/captain/backend/remote" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GetTestTimingManifest", func() { + var ( + apiClient remote.Client + mockRoundTripper func(*http.Request) (*http.Response, error) + host string + ) + + JustBeforeEach(func() { + apiClientConfig := remote.ClientConfig{Log: zap.NewNop().Sugar(), Host: host} + apiClient = remote.Client{ClientConfig: apiClientConfig, RoundTrip: mockRoundTripper} + }) + + Context("when the response is successful against captain.build", func() { + BeforeEach(func() { + host = "captain.build" + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + Expect(req.Method).To(Equal(http.MethodGet)) + Expect(req.URL.Path).To(HaveSuffix("/api/test_suites/timing_manifest")) + Expect(req.URL.Query().Has("test_suite_identifier")).To(BeTrue()) + Expect(req.URL.Query().Has("commit_sha")).To(BeTrue()) + + resp.Body = io.NopCloser(strings.NewReader(` + { + "file_timings": [ + { "file_path": "some-file", "duration": 200 } + ] + } + `)) + resp.StatusCode = 200 + return &resp, nil + } + }) + + It("returns the test file timing", func() { + testFileTiming, err := apiClient.GetTestTimingManifest(context.Background(), "test-suite-id") + Expect(err).NotTo(HaveOccurred()) + Expect(testFileTiming).To(HaveLen(1)) + }) + }) + + Context("when the response is successful", func() { + BeforeEach(func() { + host = "cloud.rwx.com" + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + Expect(req.Method).To(Equal(http.MethodGet)) + Expect(req.URL.Path).To(HaveSuffix("/captain/api/test_suites/timing_manifest")) + Expect(req.URL.Query().Has("test_suite_identifier")).To(BeTrue()) + Expect(req.URL.Query().Has("commit_sha")).To(BeTrue()) + + resp.Body = io.NopCloser(strings.NewReader(` + { + "file_timings": [ + { "file_path": "some-file", "duration": 200 } + ] + } + `)) + resp.StatusCode = 200 + return &resp, nil + } + }) + + It("returns the test file timing", func() { + testFileTiming, err := apiClient.GetTestTimingManifest(context.Background(), "test-suite-id") + Expect(err).NotTo(HaveOccurred()) + Expect(testFileTiming).To(HaveLen(1)) + }) + }) +}) diff --git a/internal/captain/backend/remote/remote_suite_test.go b/internal/captain/backend/remote/remote_suite_test.go new file mode 100644 index 0000000..4abe04a --- /dev/null +++ b/internal/captain/backend/remote/remote_suite_test.go @@ -0,0 +1,15 @@ +package remote_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestRemoteBackend(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "Remote Backend Suite") +} diff --git a/internal/captain/backend/remote/test_results_file.go b/internal/captain/backend/remote/test_results_file.go new file mode 100644 index 0000000..4d519b4 --- /dev/null +++ b/internal/captain/backend/remote/test_results_file.go @@ -0,0 +1,26 @@ +package remote + +import ( + "net/url" + + "github.com/google/uuid" + + "github.com/rwx-cloud/cli/internal/captain/fs" +) + +// ParserType is an enum holding possible parser types +type ParserType string + +// ParserTypeRWX is the parser type of RWX's test results schema +const ParserTypeRWX = ParserType("rwx") + +// TestResultsFile is a build- or test-artifact as defined by the Captain API. +type TestResultsFile struct { + ExternalID uuid.UUID `json:"external_identifier"` + FD fs.ReadOnlyFile `json:"-"` + OriginalPaths []string `json:"-"` + Parser ParserType `json:"format"` + UploadURL *url.URL + CaptainID string + S3uploadStatus int +} diff --git a/internal/captain/backend/remote/update_test_results.go b/internal/captain/backend/remote/update_test_results.go new file mode 100644 index 0000000..b1cb85a --- /dev/null +++ b/internal/captain/backend/remote/update_test_results.go @@ -0,0 +1,274 @@ +package remote + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/url" + + "github.com/google/uuid" + + "github.com/rwx-cloud/cli/internal/captain/backend" + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/fs" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +const ( + fileSizeThresholdBytes = 25 * 1024 * 1024 +) + +func (c Client) registerTestResults( + ctx context.Context, + testSuite string, + testResultsFile TestResultsFile, +) (TestResultsFile, error) { + endpoint := hostEndpointCompat(c, "/api/test_suites/bulk_test_results") + + reqBody := struct { + AttemptedBy string `json:"attempted_by"` + Provider string `json:"provider"` + BranchName string `json:"branch"` + CommitMessage *string `json:"commit_message"` + CommitSha string `json:"commit_sha"` + TestSuiteIdentifier string `json:"test_suite_identifier"` + TestResultsFiles []TestResultsFile `json:"test_results_files"` + Title *string `json:"title"` + JobTags map[string]any `json:"job_tags"` + }{ + AttemptedBy: c.Provider.AttemptedBy, + Provider: c.Provider.ProviderName, + BranchName: c.Provider.BranchName, + CommitSha: c.Provider.CommitSha, + TestSuiteIdentifier: testSuite, + TestResultsFiles: []TestResultsFile{testResultsFile}, + JobTags: c.Provider.JobTags, + } + + commitMessage := c.Provider.CommitMessage + if commitMessage != "" { + reqBody.CommitMessage = &commitMessage + } + + title := c.Provider.Title + if title != "" { + reqBody.Title = &title + } + + resp, err := c.postJSON(ctx, endpoint, reqBody) + if err != nil { + return TestResultsFile{}, err + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return TestResultsFile{}, errors.NewInternalError( + "API backend encountered an error. Endpoint was %q, Status Code %d", + endpoint, + resp.StatusCode, + ) + } + + respBody := struct { + TestResultsUploads []struct { + ExternalID uuid.UUID `json:"external_identifier"` + CaptainID string `json:"id"` + UploadURL string `json:"upload_url"` + } `json:"test_results_uploads"` + }{} + + if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil { + return TestResultsFile{}, errors.NewInternalError( + "unable to parse the response body. Endpoint was %q, Content-Type %q. Original Error: %s", + endpoint, + resp.Header.Get(headerContentType), + err, + ) + } + + for _, endpoint := range respBody.TestResultsUploads { + parsedURL, err := url.Parse(endpoint.UploadURL) + if err != nil { + return TestResultsFile{}, errors.NewInternalError("unable to parse S3 URL") + } + + if testResultsFile.ExternalID == endpoint.ExternalID { + testResultsFile.CaptainID = endpoint.CaptainID + testResultsFile.UploadURL = parsedURL + break + } + } + + return testResultsFile, nil +} + +func (c Client) updateTestResultsStatuses( + ctx context.Context, + testSuite string, + testResultsFile TestResultsFile, +) error { + endpoint := hostEndpointCompat(c, "/api/test_suites/bulk_test_results") + + type uploadStatus struct { + CaptainID string `json:"id"` + Status string `json:"upload_status"` + } + + reqBody := struct { + TestSuiteIdentifier string `json:"test_suite_identifier"` + TestResultsFiles []uploadStatus `json:"test_results_files"` + }{} + reqBody.TestSuiteIdentifier = testSuite + + switch { + case testResultsFile.S3uploadStatus < 300: + reqBody.TestResultsFiles = append(reqBody.TestResultsFiles, uploadStatus{testResultsFile.CaptainID, "uploaded"}) + default: + reqBody.TestResultsFiles = append(reqBody.TestResultsFiles, uploadStatus{testResultsFile.CaptainID, "upload_failed"}) + } + + resp, err := c.putJSON(ctx, endpoint, reqBody) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return errors.NewInternalError( + "API backend encountered an error. Endpoint was %q, Status Code %d", + endpoint, + resp.StatusCode, + ) + } + + return nil +} + +// UpdateTestResults uploads test results files to Captain. +// This method is not atomic - data-loss can occur silently. To verify that this operation was successful, +// the Captain database has to be queried manually. +func (c Client) UpdateTestResults( + ctx context.Context, + testSuite string, + testResults v1.TestResults, +) ([]backend.TestResultsUploadResult, error) { + if testSuite == "" { + return nil, errors.NewInputError("test suite name required") + } + + id, err := c.NewUUID() + if err != nil { + return nil, c.logError(errors.NewInternalError("Unable to generate new UUID: %s", err)) + } + + testResultsFile, err := c.makeTestResultsFile(testResults, id) + if err != nil { + return nil, c.logError(err) + } + + fileInfo, err := testResultsFile.FD.Stat() + if err != nil { + return nil, errors.NewSystemError("unable to determine file-size for %q", testResultsFile.FD.Name()) + } + + type stripFunc func(v1.TestResults) v1.TestResults + for _, strip := range []stripFunc{ + func(tr v1.TestResults) v1.TestResults { + c.Log.Warnf("removing original test result data from uploaded Captain test results due to content size threshold") + return v1.StripDerivedFrom(tr) + }, + func(tr v1.TestResults) v1.TestResults { + c.Log.Warnf( + "removing previous test attempt backtraces from uploaded Captain test results due to content size threshold", + ) + return v1.StripPreviousAttempts(tr) + }, + func(tr v1.TestResults) v1.TestResults { + c.Log.Warnf( + "removing current test attempt backtraces from uploaded Captain test results due to content size threshold", + ) + return v1.StripCurrentAttempts(tr) + }, + } { + if fileInfo.Size() <= fileSizeThresholdBytes { + break + } + + testResults = strip(testResults) + + testResultsFile, err = c.makeTestResultsFile(testResults, id) + if err != nil { + return nil, c.logError(err) + } + + fileInfo, err = testResultsFile.FD.Stat() + if err != nil { + return nil, errors.NewSystemError("unable to determine file-size for %q", testResultsFile.FD.Name()) + } + } + + testResultsFile, err = c.registerTestResults(ctx, testSuite, testResultsFile) + if err != nil { + return nil, err + } + + uploadResults := make([]backend.TestResultsUploadResult, 0) + if testResultsFile.UploadURL == nil { + return nil, errors.NewInternalError("endpoint failed to return upload destination url") + } + req := http.Request{ + Method: http.MethodPut, + URL: testResultsFile.UploadURL, + Body: testResultsFile.FD, + ContentLength: fileInfo.Size(), + } + + resp, err := c.RoundTrip(&req) + if err != nil { + c.Log.Warnf("unable to upload test results file to S3: %s", err) + uploadResults = append(uploadResults, backend.TestResultsUploadResult{ + OriginalPaths: uniqueStrings(testResultsFile.OriginalPaths), + Uploaded: false, + }) + } else { + testResultsFile.S3uploadStatus = resp.StatusCode + uploadResults = append(uploadResults, backend.TestResultsUploadResult{ + OriginalPaths: uniqueStrings(testResultsFile.OriginalPaths), + Uploaded: resp.StatusCode < 300, + }) + _ = resp.Body.Close() + } + + if err := c.updateTestResultsStatuses(ctx, testSuite, testResultsFile); err != nil { + c.Log.Warnf("unable to update test results file status: %s", err) + } + + return uploadResults, nil +} + +func (c Client) makeTestResultsFile(testResults v1.TestResults, externalID uuid.UUID) (TestResultsFile, error) { + buf, err := json.Marshal(testResults) + if err != nil { + return TestResultsFile{}, errors.NewInternalError("Unable to output test results as JSON: %s", err) + } + + f := fs.VirtualReadOnlyFile{ + Reader: bytes.NewReader(buf), + FileName: "rwx-test-results", + } + + originalPaths := make([]string, len(testResults.DerivedFrom)) + for i, originalTestResult := range testResults.DerivedFrom { + originalPaths[i] = originalTestResult.OriginalFilePath + } + + testResultsFile := TestResultsFile{ + ExternalID: externalID, + FD: f, + OriginalPaths: originalPaths, + Parser: ParserTypeRWX, + } + + return testResultsFile, nil +} diff --git a/internal/captain/backend/remote/update_test_results_test.go b/internal/captain/backend/remote/update_test_results_test.go new file mode 100644 index 0000000..0c8ce92 --- /dev/null +++ b/internal/captain/backend/remote/update_test_results_test.go @@ -0,0 +1,453 @@ +package remote_test + +import ( + "context" + "fmt" + "io" + "math/rand/v2" + "net/http" + "strings" + + "github.com/google/uuid" + "go.uber.org/zap" + + "github.com/rwx-cloud/cli/internal/captain/backend/remote" + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Uploading Test Results", func() { + var ( + apiClient remote.Client + testResults v1.TestResults + mockNewUUID func() (uuid.UUID, error) + mockRoundTripper func(*http.Request) (*http.Response, error) + mockRoundTripCalls int + mockUUID uuid.UUID + host string + ) + + BeforeEach(func() { + testResults = v1.TestResults{} + mockUUID = uuid.MustParse("fff24366-af1d-43cc-ab32-8c9ed137cf09") + mockNewUUID = func() (uuid.UUID, error) { + return mockUUID, nil + } + + mockRoundTripCalls = 0 + }) + + JustBeforeEach(func() { + apiClient = remote.Client{ClientConfig: remote.ClientConfig{ + Log: zap.NewNop().Sugar(), + NewUUID: mockNewUUID, + Host: host, + }, RoundTrip: mockRoundTripper} + }) + + Context("under expected conditions w/ captain.build host", func() { + BeforeEach(func() { + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + switch mockRoundTripCalls { + case 0: // registering the test results file + Expect(req.Method).To(Equal(http.MethodPost)) + Expect(req.URL.Path).To(HaveSuffix("/api/test_suites/bulk_test_results")) + Expect(req.Header.Get("Content-Type")).To(Equal("application/json")) + resp.Body = io.NopCloser(strings.NewReader(fmt.Sprintf( + "{\"test_results_uploads\":[{\"id\": %q, \"external_identifier\":%q,\"upload_url\":\"%d\"}]}", + "some-captain-identifier", mockUUID, GinkgoRandomSeed(), + ))) + case 1: // upload to `upload_url` + Expect(req.Method).To(Equal(http.MethodPut)) + Expect(req.URL.String()).To(ContainSubstring(fmt.Sprintf("%d", GinkgoRandomSeed()))) + resp.Body = io.NopCloser(strings.NewReader("")) + case 2: // update status + Expect(req.Method).To(Equal(http.MethodPut)) + Expect(req.URL.Path).To(HaveSuffix("/api/test_suites/bulk_test_results")) + body, err := io.ReadAll(req.Body) + Expect(err).ToNot(HaveOccurred()) + Expect(string(body)).To(ContainSubstring(fmt.Sprintf( + "%q,\"upload_status\":\"uploaded\"", + "some-captain-identifier", + ))) + resp.Body = io.NopCloser(strings.NewReader("")) + default: + Fail("too many HTTP calls") + } + + mockRoundTripCalls++ + + return &resp, nil + } + }) + + It("registers, uploads, and updates the test result in sequence", func() { + uploadResults, err := apiClient.UpdateTestResults(context.Background(), "test suite id", testResults) + Expect(err).To(Succeed()) + Expect(uploadResults).To(HaveLen(1)) + Expect(uploadResults[0].Uploaded).To(Equal(true)) + }) + }) + + Context("under expected conditions", func() { + BeforeEach(func() { + host = "cloud.rwx.com" + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + switch mockRoundTripCalls { + case 0: // registering the test results file + Expect(req.Method).To(Equal(http.MethodPost)) + Expect(req.URL.Path).To(HaveSuffix("/captain/api/test_suites/bulk_test_results")) + Expect(req.Header.Get("Content-Type")).To(Equal("application/json")) + resp.Body = io.NopCloser(strings.NewReader(fmt.Sprintf( + "{\"test_results_uploads\":[{\"id\": %q, \"external_identifier\":%q,\"upload_url\":\"%d\"}]}", + "some-captain-identifier", mockUUID, GinkgoRandomSeed(), + ))) + case 1: // upload to `upload_url` + Expect(req.Method).To(Equal(http.MethodPut)) + Expect(req.URL.String()).To(ContainSubstring(fmt.Sprintf("%d", GinkgoRandomSeed()))) + resp.Body = io.NopCloser(strings.NewReader("")) + case 2: // update status + Expect(req.Method).To(Equal(http.MethodPut)) + Expect(req.URL.Path).To(HaveSuffix("/captain/api/test_suites/bulk_test_results")) + body, err := io.ReadAll(req.Body) + Expect(err).ToNot(HaveOccurred()) + Expect(string(body)).To(ContainSubstring(fmt.Sprintf( + "%q,\"upload_status\":\"uploaded\"", + "some-captain-identifier", + ))) + resp.Body = io.NopCloser(strings.NewReader("")) + default: + Fail("too many HTTP calls") + } + + mockRoundTripCalls++ + + return &resp, nil + } + }) + + It("registers, uploads, and updates the test result in sequence", func() { + uploadResults, err := apiClient.UpdateTestResults(context.Background(), "test suite id", testResults) + Expect(err).To(Succeed()) + Expect(uploadResults).To(HaveLen(1)) + Expect(uploadResults[0].Uploaded).To(Equal(true)) + }) + }) + + Context("with large contents in `derivedFrom`", func() { + var err error + + BeforeEach(func() { + err = nil + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + switch mockRoundTripCalls { + case 0: // registering the test results file + Expect(req.Method).To(Equal(http.MethodPost)) + Expect(req.URL.Path).To(HaveSuffix("/api/test_suites/bulk_test_results")) + Expect(req.Header.Get("Content-Type")).To(Equal("application/json")) + resp.Body = io.NopCloser(strings.NewReader(fmt.Sprintf( + "{\"test_results_uploads\":[{\"id\": %q, \"external_identifier\":%q,\"upload_url\":\"%d\"}]}", + "some-captain-identifier", mockUUID, GinkgoRandomSeed(), + ))) + case 1: // upload to `upload_url` + Expect(req.Method).To(Equal(http.MethodPut)) + Expect(req.URL.String()).To(ContainSubstring(fmt.Sprintf("%d", GinkgoRandomSeed()))) + + body, err := io.ReadAll(req.Body) + Expect(err).ToNot(HaveOccurred()) + Expect(string(body)).To(ContainSubstring("\"contents\":\"PHRydW5jYXRlZCBkdWUgdG8gdGVzdCByZXN1bHRzIHNpemU+\"")) + + resp.Body = io.NopCloser(strings.NewReader("")) + case 2: // update status + Expect(req.Method).To(Equal(http.MethodPut)) + Expect(req.URL.Path).To(HaveSuffix("/api/test_suites/bulk_test_results")) + body, err := io.ReadAll(req.Body) + Expect(err).ToNot(HaveOccurred()) + Expect(string(body)).To(ContainSubstring(fmt.Sprintf( + "%q,\"upload_status\":\"uploaded\"", + "some-captain-identifier", + ))) + resp.Body = io.NopCloser(strings.NewReader("")) + default: + Fail("too many HTTP calls") + } + + mockRoundTripCalls++ + + return &resp, nil + } + }) + + JustBeforeEach(func() { + tooMuchData := make([]byte, 26*1024*1024) + _, _ = rand.NewChaCha8([32]byte{}).Read(tooMuchData) + + testResults = v1.TestResults{ + DerivedFrom: []v1.OriginalTestResults{{ + Contents: string(tooMuchData), + }}, + } + _, err = apiClient.UpdateTestResults(context.Background(), "test suite id", testResults) + }) + + It("strips the `derivedFrom` content from the test results", func() { + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("with large backtraces in previous attempts", func() { + var err error + + BeforeEach(func() { + err = nil + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + switch mockRoundTripCalls { + case 0: // registering the test results file + Expect(req.Method).To(Equal(http.MethodPost)) + Expect(req.URL.Path).To(HaveSuffix("/api/test_suites/bulk_test_results")) + Expect(req.Header.Get("Content-Type")).To(Equal("application/json")) + resp.Body = io.NopCloser(strings.NewReader(fmt.Sprintf( + "{\"test_results_uploads\":[{\"id\": %q, \"external_identifier\":%q,\"upload_url\":\"%d\"}]}", + "some-captain-identifier", mockUUID, GinkgoRandomSeed(), + ))) + case 1: // upload to `upload_url` + Expect(req.Method).To(Equal(http.MethodPut)) + Expect(req.URL.String()).To(ContainSubstring(fmt.Sprintf("%d", GinkgoRandomSeed()))) + + body, err := io.ReadAll(req.Body) + Expect(err).ToNot(HaveOccurred()) + Expect(string(body)).To(ContainSubstring("\"contents\":\"PHRydW5jYXRlZCBkdWUgdG8gdGVzdCByZXN1bHRzIHNpemU+\"")) + Expect(string(body)).To(ContainSubstring("\"backtrace\":[\"\\u003ctruncated due to test results size\\u003e\"]")) + Expect(string(body)).To(ContainSubstring("\"backtrace\":[\"won't be touched\"]")) + + resp.Body = io.NopCloser(strings.NewReader("")) + case 2: // update status + Expect(req.Method).To(Equal(http.MethodPut)) + Expect(req.URL.Path).To(HaveSuffix("/api/test_suites/bulk_test_results")) + body, err := io.ReadAll(req.Body) + Expect(err).ToNot(HaveOccurred()) + Expect(string(body)).To(ContainSubstring(fmt.Sprintf( + "%q,\"upload_status\":\"uploaded\"", + "some-captain-identifier", + ))) + resp.Body = io.NopCloser(strings.NewReader("")) + default: + Fail("too many HTTP calls") + } + + mockRoundTripCalls++ + + return &resp, nil + } + }) + + JustBeforeEach(func() { + tooMuchData := make([]byte, 26*1024*1024) + _, _ = rand.NewChaCha8([32]byte{}).Read(tooMuchData) + + testResults = v1.TestResults{ + Tests: []v1.Test{{ + PastAttempts: []v1.TestAttempt{{ + Status: v1.TestStatus{ + Backtrace: []string{string(tooMuchData)}, + }, + }}, + Attempt: v1.TestAttempt{ + Status: v1.TestStatus{ + Backtrace: []string{"won't be touched"}, + }, + }, + }}, + DerivedFrom: []v1.OriginalTestResults{{ + Contents: "will be stripped regardless", + }}, + } + _, err = apiClient.UpdateTestResults(context.Background(), "test suite id", testResults) + }) + + It("strips the `derivedFrom` content from the test results", func() { + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("with large backtraces in current attempt", func() { + var err error + + BeforeEach(func() { + err = nil + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + switch mockRoundTripCalls { + case 0: // registering the test results file + Expect(req.Method).To(Equal(http.MethodPost)) + Expect(req.URL.Path).To(HaveSuffix("/api/test_suites/bulk_test_results")) + Expect(req.Header.Get("Content-Type")).To(Equal("application/json")) + resp.Body = io.NopCloser(strings.NewReader(fmt.Sprintf( + "{\"test_results_uploads\":[{\"id\": %q, \"external_identifier\":%q,\"upload_url\":\"%d\"}]}", + "some-captain-identifier", mockUUID, GinkgoRandomSeed(), + ))) + case 1: // upload to `upload_url` + Expect(req.Method).To(Equal(http.MethodPut)) + Expect(req.URL.String()).To(ContainSubstring(fmt.Sprintf("%d", GinkgoRandomSeed()))) + + body, err := io.ReadAll(req.Body) + Expect(err).ToNot(HaveOccurred()) + Expect(string(body)).To(ContainSubstring("\"contents\":\"PHRydW5jYXRlZCBkdWUgdG8gdGVzdCByZXN1bHRzIHNpemU+\"")) + Expect(string(body)).To(ContainSubstring("\"backtrace\":[\"\\u003ctruncated due to test results size\\u003e\"]")) + Expect(string(body)).NotTo(ContainSubstring("\"backtrace\":[\"will be stripped regardless\"]")) + + resp.Body = io.NopCloser(strings.NewReader("")) + case 2: // update status + Expect(req.Method).To(Equal(http.MethodPut)) + Expect(req.URL.Path).To(HaveSuffix("/api/test_suites/bulk_test_results")) + body, err := io.ReadAll(req.Body) + Expect(err).ToNot(HaveOccurred()) + Expect(string(body)).To(ContainSubstring(fmt.Sprintf( + "%q,\"upload_status\":\"uploaded\"", + "some-captain-identifier", + ))) + resp.Body = io.NopCloser(strings.NewReader("")) + default: + Fail("too many HTTP calls") + } + + mockRoundTripCalls++ + + return &resp, nil + } + }) + + JustBeforeEach(func() { + tooMuchData := make([]byte, 26*1024*1024) + _, _ = rand.NewChaCha8([32]byte{}).Read(tooMuchData) + + testResults = v1.TestResults{ + Tests: []v1.Test{{ + PastAttempts: []v1.TestAttempt{{ + Status: v1.TestStatus{ + Backtrace: []string{"will be stripped regardless"}, + }, + }}, + Attempt: v1.TestAttempt{ + Status: v1.TestStatus{ + Backtrace: []string{string(tooMuchData)}, + }, + }, + }}, + DerivedFrom: []v1.OriginalTestResults{{ + Contents: "will be stripped regardless", + }}, + } + _, err = apiClient.UpdateTestResults(context.Background(), "test suite id", testResults) + }) + + It("strips the `derivedFrom` content from the test results", func() { + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("with an error during test results file registration", func() { + BeforeEach(func() { + host = "cloud.rwx.com" + mockRoundTripper = func(_ *http.Request) (*http.Response, error) { + return nil, errors.NewInternalError("Error") + } + }) + + It("returns an error to the user", func() { + uploadResults, err := apiClient.UpdateTestResults(context.Background(), "test suite id", testResults) + Expect(uploadResults).To(BeNil()) + Expect(err).ToNot(Succeed()) + }) + }) + + Context("with an error from S3", func() { + BeforeEach(func() { + host = "cloud.rwx.com" + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + switch mockRoundTripCalls { + case 0: // registering the test results file + resp.Body = io.NopCloser(strings.NewReader(fmt.Sprintf( + "{\"test_results_uploads\":[{\"id\": %q, \"external_identifier\":%q,\"upload_url\":\"%d\"}]}", + "some-captain-identifier", mockUUID, GinkgoRandomSeed(), + ))) + case 1: // upload to `upload_url` + resp.Body = io.NopCloser(strings.NewReader("")) + resp.StatusCode = 500 + case 2: // update status + body, err := io.ReadAll(req.Body) + Expect(err).ToNot(HaveOccurred()) + Expect(string(body)).To(ContainSubstring(fmt.Sprintf( + "%q,\"upload_status\":\"upload_failed\"", + "some-captain-identifier", + ))) + resp.Body = io.NopCloser(strings.NewReader("")) + default: + Fail("too many HTTP calls") + } + + mockRoundTripCalls++ + + return &resp, nil + } + }) + + It("updates the upload status as failed", func() { + uploadResults, err := apiClient.UpdateTestResults(context.Background(), "test suite id", testResults) + Expect(err).To(Succeed()) + Expect(uploadResults).To(HaveLen(1)) + Expect(uploadResults[0].Uploaded).To(Equal(false)) + }) + }) + + Context("with an error while updating an test results file status", func() { + BeforeEach(func() { + host = "cloud.rwx.com" + mockRoundTripper = func(_ *http.Request) (*http.Response, error) { + var ( + resp http.Response + err error + ) + + switch mockRoundTripCalls { + case 0: // registering the test results files + resp.Body = io.NopCloser(strings.NewReader(fmt.Sprintf( + "{\"test_results_uploads\":[{\"id\": %q, \"external_identifier\":%q,\"upload_url\":\"%d\"}]}", + "some-captain-identifier", mockUUID, GinkgoRandomSeed(), + ))) + case 1: // upload to `upload_url` + resp.Body = io.NopCloser(strings.NewReader("")) + case 2: // update status + err = errors.NewInternalError("Error") + default: + Fail("too many HTTP calls") + } + + mockRoundTripCalls++ + + return &resp, err + } + }) + + It("does not return an error to the user", func() { + uploadResults, err := apiClient.UpdateTestResults(context.Background(), "test suite id", testResults) + Expect(err).To(Succeed()) + Expect(uploadResults).To(HaveLen(1)) + Expect(uploadResults[0].Uploaded).To(Equal(true)) + }) + }) +}) diff --git a/internal/captain/backend/remote/utils.go b/internal/captain/backend/remote/utils.go new file mode 100644 index 0000000..401df74 --- /dev/null +++ b/internal/captain/backend/remote/utils.go @@ -0,0 +1,16 @@ +package remote + +func uniqueStrings(in []string) []string { + set := make(map[string]struct{}) + + for _, s := range in { + set[s] = struct{}{} + } + + out := make([]string, 0, len(set)) + for s := range set { + out = append(out, s) + } + + return out +} diff --git a/internal/captain/backend/types.go b/internal/captain/backend/types.go new file mode 100644 index 0000000..e24becb --- /dev/null +++ b/internal/captain/backend/types.go @@ -0,0 +1,40 @@ +package backend + +import ( + "context" + + "github.com/rwx-cloud/cli/internal/captain/testing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +// Client is the interface of our API layer. +type Client interface { + GetRunConfiguration(ctx context.Context, testSuiteIdentifier string) (RunConfiguration, error) + GetQuarantinedTests(ctx context.Context, testSuiteIdentifier string) ([]Test, error) + GetTestTimingManifest(context.Context, string) ([]testing.TestFileTiming, error) + UpdateTestResults(context.Context, string, v1.TestResults) ([]TestResultsUploadResult, error) +} + +type QuarantinedTest struct { + Test + QuarantinedAt string `json:"quarantined_at"` +} + +type RunConfiguration struct { + GeneratedAt string `json:"generated_at,omitempty"` + QuarantinedTests []QuarantinedTest `json:"quarantined_tests,omitempty"` + FlakyTests []Test `json:"flaky_tests,omitempty"` + OrganizationSlug string `json:"organization_slug,omitempty"` + IsSuiteQuarantined bool `json:"is_suite_quarantined,omitempty"` +} + +type Test struct { + CompositeIdentifier string `json:"composite_identifier"` + IdentityComponents []string `json:"identity_components"` + StrictIdentity bool `json:"strict_identity"` +} + +type TestResultsUploadResult struct { + OriginalPaths []string + Uploaded bool +} diff --git a/internal/captain/cli/.snapshots/Merge when given multiple globs globs each pattern and merges the results b/internal/captain/cli/.snapshots/Merge when given multiple globs globs each pattern and merges the results new file mode 100644 index 0000000..9be3c38 --- /dev/null +++ b/internal/captain/cli/.snapshots/Merge when given multiple globs globs each pattern and merges the results @@ -0,0 +1,144 @@ +(v1.TestResults) { + Framework: (v1.Framework) Other: one (one), + Summary: (v1.Summary) { + Status: (v1.SummaryStatus) (len=10) "successful", + Tests: (int) 4, + Flaky: (int) 0, + OtherErrors: (int) 0, + Retries: (int) 0, + Canceled: (int) 0, + Failed: (int) 0, + Pended: (int) 0, + Quarantined: (int) 0, + Skipped: (int) 0, + Successful: (int) 0, + TimedOut: (int) 0, + Todo: (int) 0 + }, + Tests: ([]v1.Test) (len=4) { + (v1.Test) { + ID: (*string)((len=7) "test-id"), + Name: (string) (len=9) "test-name", + Scope: (*string)(), + Lineage: ([]string) { + }, + Location: (*v1.Location)(test-file), + Attempt: (v1.TestAttempt) { + Duration: (*time.Duration)(), + Meta: (map[string]interface {}) , + Status: (v1.TestStatus) { + Kind: (v1.TestStatusKind) "", + OriginalStatus: (*v1.TestStatus)(), + Message: (*string)(), + Exception: (*string)(), + Backtrace: ([]string) + }, + Stderr: (*string)(), + Stdout: (*string)(), + StartedAt: (*time.Time)(), + FinishedAt: (*time.Time)() + }, + PastAttempts: ([]v1.TestAttempt) { + } + }, + (v1.Test) { + ID: (*string)((len=7) "test-id"), + Name: (string) (len=9) "test-name", + Scope: (*string)(), + Lineage: ([]string) { + }, + Location: (*v1.Location)(test-file), + Attempt: (v1.TestAttempt) { + Duration: (*time.Duration)(), + Meta: (map[string]interface {}) , + Status: (v1.TestStatus) { + Kind: (v1.TestStatusKind) "", + OriginalStatus: (*v1.TestStatus)(), + Message: (*string)(), + Exception: (*string)(), + Backtrace: ([]string) + }, + Stderr: (*string)(), + Stdout: (*string)(), + StartedAt: (*time.Time)(), + FinishedAt: (*time.Time)() + }, + PastAttempts: ([]v1.TestAttempt) { + } + }, + (v1.Test) { + ID: (*string)((len=7) "test-id"), + Name: (string) (len=9) "test-name", + Scope: (*string)(), + Lineage: ([]string) { + }, + Location: (*v1.Location)(test-file), + Attempt: (v1.TestAttempt) { + Duration: (*time.Duration)(), + Meta: (map[string]interface {}) , + Status: (v1.TestStatus) { + Kind: (v1.TestStatusKind) "", + OriginalStatus: (*v1.TestStatus)(), + Message: (*string)(), + Exception: (*string)(), + Backtrace: ([]string) + }, + Stderr: (*string)(), + Stdout: (*string)(), + StartedAt: (*time.Time)(), + FinishedAt: (*time.Time)() + }, + PastAttempts: ([]v1.TestAttempt) { + } + }, + (v1.Test) { + ID: (*string)((len=7) "test-id"), + Name: (string) (len=9) "test-name", + Scope: (*string)(), + Lineage: ([]string) { + }, + Location: (*v1.Location)(test-file), + Attempt: (v1.TestAttempt) { + Duration: (*time.Duration)(), + Meta: (map[string]interface {}) , + Status: (v1.TestStatus) { + Kind: (v1.TestStatusKind) "", + OriginalStatus: (*v1.TestStatus)(), + Message: (*string)(), + Exception: (*string)(), + Backtrace: ([]string) + }, + Stderr: (*string)(), + Stdout: (*string)(), + StartedAt: (*time.Time)(), + FinishedAt: (*time.Time)() + }, + PastAttempts: ([]v1.TestAttempt) { + } + } + }, + OtherErrors: ([]v1.OtherError) , + DerivedFrom: ([]v1.OriginalTestResults) (len=4) { + (v1.OriginalTestResults) { + OriginalFilePath: (string) "", + Contents: (string) "", + GroupNumber: (int) 1 + }, + (v1.OriginalTestResults) { + OriginalFilePath: (string) "", + Contents: (string) "", + GroupNumber: (int) 1 + }, + (v1.OriginalTestResults) { + OriginalFilePath: (string) "", + Contents: (string) "", + GroupNumber: (int) 1 + }, + (v1.OriginalTestResults) { + OriginalFilePath: (string) "", + Contents: (string) "", + GroupNumber: (int) 1 + } + }, + Meta: (map[string]interface {}) +} diff --git a/internal/captain/cli/cli_suite_test.go b/internal/captain/cli/cli_suite_test.go new file mode 100644 index 0000000..7360c5b --- /dev/null +++ b/internal/captain/cli/cli_suite_test.go @@ -0,0 +1,15 @@ +package cli_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestCli(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "Cli Suite") +} diff --git a/internal/captain/cli/config.go b/internal/captain/cli/config.go new file mode 100644 index 0000000..aa8f69c --- /dev/null +++ b/internal/captain/cli/config.go @@ -0,0 +1,237 @@ +package cli + +import ( + "fmt" + "regexp" + "strconv" + + "go.uber.org/zap" + + "github.com/rwx-cloud/cli/internal/captain/config" + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +// RunConfig holds the configuration for running a test suite (used by `RunSuite`) +type RunConfig struct { + Args []string + CloudOrganizationSlug string + Command string + TestResultsFileGlob string + FailOnUploadError bool + FailOnMisconfiguredRetry bool + FailRetriesFast bool + FlakyRetries int + IntermediateArtifactsPath string + AdditionalArtifactPaths []string + MaxTestsToRetry string + PostRetryCommands []string + PreRetryCommands []string + PrintSummary bool + Quiet bool + Reporters map[string]Reporter + Retries int + RetryCommandTemplate string + SuiteID string + SubstitutionsByFramework map[v1.Framework]targetedretries.Substitution + UploadResults bool + PartitionCommandTemplate string + PartitionConfig PartitionConfig + PartitionRoundRobin bool + PartitionTrimPrefix string + WriteRetryFailedTestsAction bool + DidRetryFailedTestsInMint bool + QuarantinedTestRetries int +} + +var maxTestsToRetryRegexp = regexp.MustCompile( + `^\s*(?P\d+)\s*$|^\s*(?:(?P\d+(?:\.\d+)?)%)\s*$`, +) + +func (rc RunConfig) Validate(log *zap.SugaredLogger) error { + if rc.RetryCommandTemplate == "" && (rc.Retries > 0 || rc.FlakyRetries > 0) { + return errors.NewConfigurationError( + "Missing retry command", + "You seem to have retries enabled, but there is no retry command template configured.", + "The retry command template can be set using the --retry-command flag. Alternatively, you can "+ + "use the rwx test configuration file to permanently set a command template for a "+ + "given test suite.", + ) + } + + if rc.MaxTestsToRetry != "" && !maxTestsToRetryRegexp.MatchString(rc.MaxTestsToRetry) { + return errors.NewConfigurationError( + "Unsupported --max-tests-to-retry value", + fmt.Sprintf( + "rwx test is unable to parse the --max-tests-to-retry option, which is currently set to %q", + rc.MaxTestsToRetry, + ), + "It is expected that this option is either set to a positive integer or to a percentage of the total "+ + "tests to retry. Percentages can be fractional.", + ) + } + + if rc.MaxTestsToRetry != "" && rc.Retries <= 0 && rc.FlakyRetries <= 0 { + log.Warn("The --max-tests-to-retry flag has no effect as no retries are otherwise configured.") + } + + if len(rc.AdditionalArtifactPaths) > 0 && rc.IntermediateArtifactsPath == "" { + return errors.NewConfigurationError( + "Missing intermediate artifacts path", + "You have specified additional artifact paths, but no intermediate artifacts path is configured.", + "The intermediate artifacts path is required when using additional artifact paths. "+ + "Set it using the --intermediate-artifacts-path flag.", + ) + } + + if rc.PartitionCommandTemplate != "" && rc.PartitionConfig.PartitionNodes.Total <= 1 { + log.Warnf("There is a partition command configured for this test suite, but partitioning is disabled.") + } + + if rc.IsRunningPartition() { + err := rc.PartitionConfig.Validate() + if err != nil { + return errors.WithStack(err) + } + } + + return nil +} + +func (rc RunConfig) MaxTestsToRetryCount() (*int, error) { + if rc.MaxTestsToRetry == "" { + return nil, nil + } + + match := maxTestsToRetryRegexp.FindStringSubmatch(rc.MaxTestsToRetry) + result := map[string]string{} + for i, name := range maxTestsToRetryRegexp.SubexpNames() { + if i != 0 && name != "" { + result[name] = match[i] + } + } + + value := result["failureCount"] + if value == "" { + return nil, nil + } + + count, err := strconv.Atoi(value) + if err != nil { + return nil, errors.WithStack(err) + } + + return &count, nil +} + +func (rc RunConfig) MaxTestsToRetryPercentage() (*float64, error) { + if rc.MaxTestsToRetry == "" { + return nil, nil + } + + match := maxTestsToRetryRegexp.FindStringSubmatch(rc.MaxTestsToRetry) + result := map[string]string{} + for i, name := range maxTestsToRetryRegexp.SubexpNames() { + if i != 0 && name != "" { + result[name] = match[i] + } + } + + value := result["failurePercentage"] + if value == "" { + return nil, nil + } + + percentage, err := strconv.ParseFloat(value, 64) + if err != nil { + return nil, errors.WithStack(err) + } + + return &percentage, nil +} + +func (rc RunConfig) IsRunningPartition() bool { + // TODO: Should we have a bit somewhere that indicates provider defaulted? + return rc.PartitionCommandTemplate != "" && rc.PartitionConfig.PartitionNodes.Total >= 1 +} + +type PartitionConfig struct { + SuiteID string + TestFilePaths []string + Delimiter string + PartitionNodes config.PartitionNodes + RoundRobin bool + TrimPrefix string +} + +func (pc PartitionConfig) Validate() error { + if pc.SuiteID == "" { + return errors.NewConfigurationError( + "Missing suite ID", + "A suite ID is required in order to use the partitioning feature.", + "The suite ID can be set using the --suite-id flag or setting a RWX_TEST_SUITE_ID environment variable", + ) + } + + if pc.PartitionNodes.Total <= 0 { + return errors.NewConfigurationError( + "Missing total partition count", + "In order to use the partitioning feature, rwx test needs to know the total number of partitions.\n", + "When using the run command, the total number of partitions can be set using the --partition-total flag.\n\n"+ + "When using the partition command, the total number of partitions can be set using the --total flag or "+ + "alternatively the RWX_TEST_PARTITION_TOTAL environment variable.", + ) + } + + if pc.PartitionNodes.Index < 0 { + return errors.NewConfigurationError( + "Missing partition index", + "rwx test is missing the index of the partition that you would like to generate.\n", + "When using the run command, partition index can be set using the --partition-index flag.\n\n"+ + "When using the partition command, partition index can be set using the --index flag "+ + "or alternatively the RWX_TEST_PARTITION_INDEX environment variable.", + ) + } + + if pc.PartitionNodes.Index >= pc.PartitionNodes.Total { + return errors.NewConfigurationError( + "Unsupported partitioning setup", + fmt.Sprintf( + "You specified a partition index (%d) that is greater than or equal to the total number of partitions (%d)", + pc.PartitionNodes.Index, pc.PartitionNodes.Total, + ), + "Please set the index to be below the total number of partitions. Note that rwx test uses '0' as the first "+ + "partition number", + ) + } + + if len(pc.TestFilePaths) == 0 { + return errors.NewConfigurationError( + "Missing test file paths", + "No test file paths are provided.\n", + "When using the run command, please specify the path or paths to your test files using the --partition-globs flag. "+ + "You may specify this flag multiple times if needed.\n\n"+ + "When using the partition command, please specify the path or paths to your test files as arguments.\n\n"+ + "\trwx test partition [flags] \n\n"+ + "You can also execute 'rwx test partition --help' for further information.", + ) + } + + return nil +} + +type QuarantineConfig struct { + Args []string + TestResultsFileGlob string + PrintSummary bool + Quiet bool + Reporters map[string]Reporter + SuiteID string +} + +type MergeConfig struct { + ResultsGlobs []string + PrintSummary bool + Reporters map[string]Reporter +} diff --git a/internal/captain/cli/config_file.go b/internal/captain/cli/config_file.go new file mode 100644 index 0000000..a88a143 --- /dev/null +++ b/internal/captain/cli/config_file.go @@ -0,0 +1,60 @@ +package cli + +// configFile holds all options that can be set over the config file +type ConfigFile struct { + Cloud struct { + APIHost string `yaml:"api-host"` + Insecure bool + } + Flags map[string]any + Output struct { + Debug bool + } + TestSuites map[string]SuiteConfig `yaml:"test-suites"` +} + +type SuiteConfigOutput struct { + PrintSummary bool `yaml:"print-summary"` + Reporters map[string]string + Quiet bool +} + +type SuiteConfigResults struct { + Framework string + Language string + Path string +} + +type SuiteConfigRetries struct { + Attempts int + Command string + FailFast bool `yaml:"fail-fast"` + FailOnMisconfiguration bool `yaml:"fail-on-misconfiguration"` + FlakyAttempts int `yaml:"flaky-attempts"` + MaxTests string `yaml:"max-tests"` + MaxTestsLegacyName string `yaml:"maxtests"` + PostRetryCommands []string `yaml:"post-retry-commands"` + PreRetryCommands []string `yaml:"pre-retry-commands"` + IntermediateArtifactsPath string `yaml:"intermediate-artifacts-path"` + AdditionalArtifactPaths []string `yaml:"additional-artifact-paths"` + QuarantinedAttempts int `yaml:"quarantined-attempts"` +} + +type SuiteConfigPartition struct { + Command string + Globs []string + Delimiter string + RoundRobin bool `yaml:"round-robin"` + TrimPrefix string `yaml:"trim-prefix"` +} + +// SuiteConfig holds options that can be customized per suite +type SuiteConfig struct { + Command string + FailOnUploadError bool `yaml:"fail-on-upload-error"` + FailOnDuplicateTestID bool `yaml:"fail-on-duplicate-test-id"` + Output SuiteConfigOutput + Results SuiteConfigResults + Retries SuiteConfigRetries + Partition SuiteConfigPartition +} diff --git a/internal/captain/cli/config_test.go b/internal/captain/cli/config_test.go new file mode 100644 index 0000000..768520f --- /dev/null +++ b/internal/captain/cli/config_test.go @@ -0,0 +1,300 @@ +package cli_test + +import ( + "go.uber.org/zap/zaptest" + + "github.com/rwx-cloud/cli/internal/captain/cli" + "github.com/rwx-cloud/cli/internal/captain/config" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("RunConfig", func() { + logger := zaptest.NewLogger(GinkgoT()).Sugar() + + Describe("Validate", func() { + It("errs when max-tests-to-retry is neither an integer nor float percentage", func() { + var err error + + err = cli.RunConfig{MaxTestsToRetry: "1.5"}.Validate(logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unsupported --max-tests-to-retry value")) + + err = cli.RunConfig{MaxTestsToRetry: "something"}.Validate(logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unsupported --max-tests-to-retry value")) + + err = cli.RunConfig{MaxTestsToRetry: "something%"}.Validate(logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unsupported --max-tests-to-retry value")) + + err = cli.RunConfig{MaxTestsToRetry: " 1 "}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + + err = cli.RunConfig{MaxTestsToRetry: " 1.5% "}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + + err = cli.RunConfig{MaxTestsToRetry: " 1% "}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + + err = cli.RunConfig{MaxTestsToRetry: ""}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("errs when retries are positive and the retry command is missing", func() { + err := cli.RunConfig{Retries: 1, RetryCommandTemplate: ""}.Validate(logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing retry command")) + }) + + It("errs when flaky-retries are positive and the retry command is missing", func() { + err := cli.RunConfig{FlakyRetries: 1, RetryCommandTemplate: ""}.Validate(logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing retry command")) + }) + + It("is valid when retries are positive and the retry command is present", func() { + err := cli.RunConfig{Retries: 1, RetryCommandTemplate: "some-command"}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is valid when flaky-retries are positive and the retry command is present", func() { + err := cli.RunConfig{FlakyRetries: 1, RetryCommandTemplate: "some-command"}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is valid when retries are 0 and the retry command is missing", func() { + err := cli.RunConfig{Retries: 0, RetryCommandTemplate: ""}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is valid when flaky-retries are 0 and the retry command is missing", func() { + err := cli.RunConfig{FlakyRetries: 0, RetryCommandTemplate: ""}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is valid when retries are 0 and the retry command is present", func() { + err := cli.RunConfig{Retries: 0, RetryCommandTemplate: "some-command"}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is valid when flaky-retries are 0 and the retry command is present", func() { + err := cli.RunConfig{FlakyRetries: 0, RetryCommandTemplate: "some-command"}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is valid when retries are negative and the retry command is missing", func() { + err := cli.RunConfig{Retries: -1, RetryCommandTemplate: ""}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is valid when flaky-retries are negative and the retry command is missing", func() { + err := cli.RunConfig{FlakyRetries: -1, RetryCommandTemplate: ""}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is valid when retries are negative and the retry command is present", func() { + err := cli.RunConfig{Retries: -1, RetryCommandTemplate: "some-command"}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is valid when flaky-retries are negative and the retry command is present", func() { + err := cli.RunConfig{FlakyRetries: -1, RetryCommandTemplate: "some-command"}.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("errs when additional artifact paths are provided without intermediate artifacts path", func() { + err := cli.RunConfig{ + AdditionalArtifactPaths: []string{"coverage/**/*"}, + IntermediateArtifactsPath: "", + }.Validate(logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing intermediate artifacts path")) + }) + + It("is valid when additional artifact paths are provided with intermediate artifacts path", func() { + err := cli.RunConfig{ + AdditionalArtifactPaths: []string{"coverage/**/*"}, + IntermediateArtifactsPath: "/tmp/artifacts", + }.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is valid when no additional artifact paths are provided", func() { + err := cli.RunConfig{ + AdditionalArtifactPaths: []string{}, + IntermediateArtifactsPath: "", + }.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("errs when partitioning and partition config is missing suite id", func() { + err := cli.RunConfig{ + PartitionCommandTemplate: "something {{ testFiles }}", + PartitionConfig: cli.PartitionConfig{ + PartitionNodes: config.PartitionNodes{ + Index: 0, + Total: 1, + }, + }, + }.Validate(logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing suite ID")) + }) + + It("errs when partitioning and partition config is missing test file paths", func() { + err := cli.RunConfig{ + PartitionCommandTemplate: "something {{ testFiles }}", + PartitionConfig: cli.PartitionConfig{ + SuiteID: "your-suite", + PartitionNodes: config.PartitionNodes{ + Index: 0, + Total: 1, + }, + }, + }.Validate(logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing test file paths")) + }) + + It("errs when partition config has out of bound indices", func() { + err := cli.RunConfig{ + PartitionCommandTemplate: "something {{ testFiles }}", + PartitionConfig: cli.PartitionConfig{ + SuiteID: "your-suite", + PartitionNodes: config.PartitionNodes{ + Index: 2, + Total: 1, + }, + }, + }.Validate(logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unsupported partitioning setup")) + }) + + It("errs when partitioning and partition config has nonsense index", func() { + err := cli.RunConfig{ + PartitionCommandTemplate: "something {{ testFiles }}", + PartitionConfig: cli.PartitionConfig{ + SuiteID: "your-suite", + PartitionNodes: config.PartitionNodes{ + Index: -1, + Total: 1, + }, + }, + }.Validate(logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing partition index")) + }) + + It("is not an error when partitioning is configured but not currently partitioning", func() { + err := cli.RunConfig{ + PartitionCommandTemplate: "something {{ testFiles }}", + PartitionConfig: cli.PartitionConfig{ + SuiteID: "your-suite", + PartitionNodes: config.PartitionNodes{ + Index: -1, + Total: -1, + }, + }, + }.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is valid when partitioning and partition config has command and globs", func() { + err := cli.RunConfig{ + PartitionCommandTemplate: "something {{ testFiles }}", + PartitionConfig: cli.PartitionConfig{ + SuiteID: "your-suite", + PartitionNodes: config.PartitionNodes{ + Index: 1, + Total: 2, + }, + TestFilePaths: []string{"spec/**/*_spec.rb"}, + }, + }.Validate(logger) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("MaxTestsToRetryCount", func() { + It("returns the count when set", func() { + count, err := cli.RunConfig{MaxTestsToRetry: "1"}.MaxTestsToRetryCount() + Expect(err).NotTo(HaveOccurred()) + Expect(*count).To(Equal(1)) + }) + + It("returns nil when not set", func() { + count, err := cli.RunConfig{MaxTestsToRetry: ""}.MaxTestsToRetryCount() + Expect(err).NotTo(HaveOccurred()) + Expect(count).To(BeNil()) + }) + + It("returns nil when a percentage", func() { + count, err := cli.RunConfig{MaxTestsToRetry: "1.5%"}.MaxTestsToRetryCount() + Expect(err).NotTo(HaveOccurred()) + Expect(count).To(BeNil()) + }) + }) + + Describe("MaxTestsToRetryPercentage", func() { + It("returns the percentage when set", func() { + percentage, err := cli.RunConfig{MaxTestsToRetry: "1.5%"}.MaxTestsToRetryPercentage() + Expect(err).NotTo(HaveOccurred()) + Expect(*percentage).To(Equal(1.5)) + }) + + It("returns nil when not set", func() { + percentage, err := cli.RunConfig{MaxTestsToRetry: ""}.MaxTestsToRetryPercentage() + Expect(err).NotTo(HaveOccurred()) + Expect(percentage).To(BeNil()) + }) + + It("returns nil when a count", func() { + percentage, err := cli.RunConfig{MaxTestsToRetry: "1"}.MaxTestsToRetryPercentage() + Expect(err).NotTo(HaveOccurred()) + Expect(percentage).To(BeNil()) + }) + }) + + Describe("IsRunningPartition", func() { + It("returns false when partition command template is not set", func() { + Expect(cli.RunConfig{}.IsRunningPartition()).To(Equal(false)) + }) + + It("returns false when partition command template is set, but partition nodes are defaulted to -1", func() { + rc := cli.RunConfig{ + PartitionCommandTemplate: "bin/rspec {{testFiles}}", + PartitionConfig: cli.PartitionConfig{ + PartitionNodes: config.PartitionNodes{ + Index: -1, + Total: -1, + }, + }, + } + Expect(rc.IsRunningPartition()).To(Equal(false)) + }) + + It("returns false when partition command template is set, but neither partition nodes are unset", func() { + rc := cli.RunConfig{ + PartitionCommandTemplate: "bin/rspec {{testFiles}}", + PartitionConfig: cli.PartitionConfig{}, + } + Expect(rc.IsRunningPartition()).To(Equal(false)) + }) + + It("returns true when partition command template and partition total is set", func() { + rc := cli.RunConfig{ + PartitionCommandTemplate: "bin/rspec {{testFiles}}", + PartitionConfig: cli.PartitionConfig{ + PartitionNodes: config.PartitionNodes{ + Index: 0, + Total: 2, + }, + }, + } + Expect(rc.IsRunningPartition()).To(Equal(true)) + }) + }) +}) diff --git a/internal/captain/cli/interfaces.go b/internal/captain/cli/interfaces.go new file mode 100644 index 0000000..06cce7e --- /dev/null +++ b/internal/captain/cli/interfaces.go @@ -0,0 +1,21 @@ +package cli + +import ( + "context" + + "github.com/rwx-cloud/cli/internal/captain/exec" + "github.com/rwx-cloud/cli/internal/captain/fs" + "github.com/rwx-cloud/cli/internal/captain/reporting" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +// Reporter is a function that writes test results to a file. Different reporters implement different encodings. +type Reporter func(fs.File, v1.TestResults, reporting.Configuration) error + +// TaskRunner is an abstraction over various task-runners / execution environments. +// They are expected to implement the `taskRunner.Command` interface in turn, which is mapped to the Command type from +// `os/exec` +type TaskRunner interface { + NewCommand(ctx context.Context, cfg exec.CommandConfig) (exec.Command, error) + GetExitStatusFromError(error) (int, error) +} diff --git a/internal/captain/cli/merge.go b/internal/captain/cli/merge.go new file mode 100644 index 0000000..d5fa085 --- /dev/null +++ b/internal/captain/cli/merge.go @@ -0,0 +1,53 @@ +package cli + +import ( + "context" + "os" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/providers" + "github.com/rwx-cloud/cli/internal/captain/reporting" +) + +// Parse parses the files supplied in `filepaths` and reports them as specified +func (s Service) Merge(_ context.Context, cfg MergeConfig) error { + testResultsFiles := make([]string, 0) + for _, resultsGlob := range cfg.ResultsGlobs { + files, err := s.FileSystem.Glob(resultsGlob) + if err != nil { + return errors.NewSystemError("unable to expand %q: %s", resultsGlob, err) + } + + testResultsFiles = append(testResultsFiles, files...) + } + + results, err := s.parse(testResultsFiles, 1) + if err != nil { + return errors.WithStack(err) + } + + reportingConfiguration := reporting.Configuration{ + CloudEnabled: false, + CloudHost: "", + CloudOrganizationSlug: "", + Provider: providers.Provider{}, + } + + for outputPath, writeReport := range cfg.Reporters { + file, err := s.FileSystem.Create(outputPath) + if err == nil { + err = writeReport(file, *results, reportingConfiguration) + } + if err != nil { + s.Log.Warnf("Unable to write report to %s: %s", outputPath, err.Error()) + } + } + + if cfg.PrintSummary { + if err := reporting.WriteTextSummary(os.Stdout, *results, reportingConfiguration); err != nil { + s.Log.Warnf("Unable to write text summary to stdout: %s", err.Error()) + } + } + + return nil +} diff --git a/internal/captain/cli/merge_test.go b/internal/captain/cli/merge_test.go new file mode 100644 index 0000000..6800b4f --- /dev/null +++ b/internal/captain/cli/merge_test.go @@ -0,0 +1,130 @@ +package cli_test + +import ( + "context" + "io" + "sort" + "strings" + + "github.com/bradleyjkemp/cupaloy" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" + "go.uber.org/zap/zaptest/observer" + + "github.com/rwx-cloud/cli/internal/captain/cli" + "github.com/rwx-cloud/cli/internal/captain/fs" + "github.com/rwx-cloud/cli/internal/captain/mocks" + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/reporting" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +type SuccessfulParser struct{} + +func (p SuccessfulParser) Parse(_ io.Reader) (*v1.TestResults, error) { + id := "test-id" + one := "one" + return &v1.TestResults{Summary: v1.Summary{Tests: 1}, Tests: []v1.Test{ + { + ID: &id, + Name: "test-name", + Lineage: []string{}, + Location: &v1.Location{File: "test-file"}, + Attempt: v1.TestAttempt{}, + PastAttempts: []v1.TestAttempt{}, + }, + }, Framework: v1.NewOtherFramework(&one, &one)}, nil +} + +var _ = Describe("Merge", func() { + var ( + ctx context.Context + service cli.Service + core zapcore.Core + mockFilesystem *mocks.FileSystem + ) + + BeforeEach(func() { + core, _ = observer.New(zapcore.DebugLevel) + mockFilesystem = new(mocks.FileSystem) + logger := zaptest.NewLogger(GinkgoT(), zaptest.WrapOptions( + zap.WrapCore(func(_ zapcore.Core) zapcore.Core { return core }), + )).Sugar() + service = cli.Service{ + Log: logger, + FileSystem: mockFilesystem, + ParseConfig: parsing.Config{ + Logger: logger, + MutuallyExclusiveParsers: []parsing.Parser{}, + GenericParsers: []parsing.Parser{SuccessfulParser{}}, + }, + } + }) + + Context("when given multiple globs", func() { + It("globs each pattern and merges the results", func() { + didGlobJSON := false + didGlobXML := false + + mockGlob := func(pattern string) ([]string, error) { + switch pattern { + case "*.json": + didGlobJSON = true + return []string{"a.json", "b.json"}, nil + case "*.xml": + didGlobXML = true + return []string{"a.xml", "b.xml"}, nil + } + + return nil, nil + } + mockFilesystem.MockGlob = mockGlob + + openedFiles := []string{} + + mockOpen := func(name string) (fs.File, error) { + openedFiles = append(openedFiles, name) + + return &mocks.File{ + Builder: new(strings.Builder), + Reader: strings.NewReader(""), + }, nil + } + mockFilesystem.MockOpen = mockOpen + + mockCreate := func(_ string) (fs.File, error) { + return &mocks.File{ + Builder: new(strings.Builder), + Reader: strings.NewReader(""), + }, nil + } + mockFilesystem.MockCreate = mockCreate + + var reportedResults *v1.TestResults + didReport := false + err := service.Merge(ctx, cli.MergeConfig{ + ResultsGlobs: []string{"*.json", "*.xml"}, + Reporters: map[string]cli.Reporter{ + "test-reporter": func(_ fs.File, tr v1.TestResults, _ reporting.Configuration) error { + didReport = true + reportedResults = &tr + return nil + }, + }, + }) + + Expect(err).NotTo(HaveOccurred()) + Expect(didGlobJSON).To(BeTrue()) + Expect(didGlobXML).To(BeTrue()) + Expect(didReport).To(BeTrue()) + + sort.Strings(openedFiles) + Expect(openedFiles).To(Equal([]string{"a.json", "a.xml", "b.json", "b.xml"})) + cupaloy.SnapshotT(GinkgoT(), *reportedResults) + }) + }) +}) diff --git a/internal/captain/cli/parse.go b/internal/captain/cli/parse.go new file mode 100644 index 0000000..da68145 --- /dev/null +++ b/internal/captain/cli/parse.go @@ -0,0 +1,104 @@ +package cli + +import ( + "context" + "encoding/json" + "os" + "strconv" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +func firstNonEmpty(values ...string) string { + for _, v := range values { + if v != "" { + return v + } + } + return "" +} + +// Parse parses the files supplied in `filepaths` and prints them as formatted JSON to stdout. +func (s Service) Parse(_ context.Context, filepaths []string) error { + testResultsFiles := make([]string, 0) + for _, filepath := range filepaths { + files, err := s.FileSystem.Glob(filepath) + if err != nil { + return errors.NewSystemError("unable to expand filepath glob: %s", err) + } + + testResultsFiles = append(testResultsFiles, files...) + } + + results, err := s.parse(testResultsFiles, 1) + if err != nil { + return errors.WithStack(err) + } + + // Apply stripping based on environment variables + if (os.Getenv("RWX_TEST_STRIP_DERIVED_FROM") != "" || os.Getenv("CAPTAIN_STRIP_DERIVED_FROM") != "") && results != nil { + s.Log.Warnf("removing original test result data from test results due to RWX_TEST_STRIP_DERIVED_FROM") + stripped := v1.StripDerivedFrom(*results) + results = &stripped + } + + if maxSizeStr := firstNonEmpty(os.Getenv("RWX_TEST_MAX_FILE_SIZE_IN_MEGABYTES"), os.Getenv("CAPTAIN_MAX_FILE_SIZE_IN_MEGABYTES")); maxSizeStr != "" && results != nil { + maxSizeMB, err := strconv.ParseFloat(maxSizeStr, 64) + if err == nil && maxSizeMB > 0 { + fileSizeThresholdBytes := int64(maxSizeMB * 1024 * 1024) + stripped := v1.StripToSize(*results, fileSizeThresholdBytes) + results = &stripped + } + } + + newOutput, err := json.MarshalIndent(results, "", " ") + if err != nil { + return errors.NewInternalError("Unable to output test results as JSON: %s", err) + } + s.Log.Infoln(string(newOutput)) + + return nil +} + +func (s Service) parse(filepaths []string, group int) (*v1.TestResults, error) { + var framework *v1.Framework + allResults := make([]v1.TestResults, 0) + + for _, testResultsFilePath := range filepaths { + s.Log.Debugf("Attempting to parse %q", testResultsFilePath) + + fd, err := s.FileSystem.Open(testResultsFilePath) + if err != nil { + return nil, errors.NewSystemError("unable to open file: %s", err) + } + defer fd.Close() + + results, err := parsing.Parse(fd, group, s.ParseConfig) + if err != nil { + if _, ok := errors.AsDuplicateTestIDError(err); ok { + return nil, errors.WithStack(err) + } + + return nil, errors.NewInputError("Unable to parse %q with the available parsers", testResultsFilePath) + } + + if framework == nil { + framework = &results.Framework + } else if !framework.Equal(results.Framework) { + return nil, errors.NewInputError( + "Multiple frameworks detected. rwx test only works with one framework at a time", + ) + } + + allResults = append(allResults, *results) + } + + if len(allResults) == 0 { + return nil, nil + } + + mergedResults := v1.Merge(allResults) + return &mergedResults, nil +} diff --git a/internal/captain/cli/partition.go b/internal/captain/cli/partition.go new file mode 100644 index 0000000..603f50b --- /dev/null +++ b/internal/captain/cli/partition.go @@ -0,0 +1,167 @@ +package cli + +import ( + "context" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/testing" +) + +// Partition splits a glob of test filepaths using decreasing first fit backed by a timing manifest from captain. +func (s Service) Partition(ctx context.Context, cfg PartitionConfig) error { + err := cfg.Validate() + if err != nil { + return errors.WithStack(err) + } + partitionResult, err := s.calculatePartition(ctx, cfg) + if err != nil { + return err + } + s.Log.Infoln(strings.Join(partitionResult.partition.TestFilePaths, cfg.Delimiter)) + return nil +} + +func (s Service) calculatePartition(ctx context.Context, cfg PartitionConfig) (PartitionResult, error) { + fileTimingMatches := make([]testing.FileTimingMatch, 0) + unmatchedFilepaths := make([]string, 0) + testFilePaths, err := s.FileSystem.GlobMany(cfg.TestFilePaths) + if err != nil { + return PartitionResult{}, errors.NewSystemError("unable to expand filepath glob: %s", err) + } + + if cfg.RoundRobin { + unmatchedFilepaths = append(unmatchedFilepaths, testFilePaths...) + } else { + fileTimings, err := s.API.GetTestTimingManifest(ctx, cfg.SuiteID) + if err != nil { + return PartitionResult{}, errors.WithStack(err) + } + + // Compare expanded client file paths w/ expanded server file paths + // taking care to always use the client path and sort by duration desc + for _, clientTestFile := range testFilePaths { + match := false + var fileTimingMatch testing.FileTimingMatch + clientExpandedFilepath := clientTestFile + if cfg.TrimPrefix != "" { + trimmedClientExpandedFilepath := strings.TrimPrefix(clientTestFile, cfg.TrimPrefix) + s.Log.Debugf( + "Trimming prefix '%s' from '%s' resulting in '%s' for comparison", + cfg.TrimPrefix, + clientTestFile, + trimmedClientExpandedFilepath, + ) + clientExpandedFilepath = trimmedClientExpandedFilepath + } + clientExpandedFilepath, err = filepath.Abs(clientExpandedFilepath) + if err != nil { + s.Log.Warnf("failed to expand path of test file: %s", clientTestFile) + unmatchedFilepaths = append(unmatchedFilepaths, clientTestFile) + continue + } + + for _, serverTiming := range fileTimings { + serverExpandedFilepath, err := filepath.Abs(serverTiming.Filepath) + if err != nil { + s.Log.Warnf("failed to expand filepath of timing file: %s", serverTiming.Filepath) + break + } + if clientExpandedFilepath == serverExpandedFilepath { + match = true + fileTimingMatch = testing.FileTimingMatch{ + FileTiming: serverTiming, + ClientFilepath: clientTestFile, + } + break + } + } + if match { + fileTimingMatches = append(fileTimingMatches, fileTimingMatch) + } else { + unmatchedFilepaths = append(unmatchedFilepaths, clientTestFile) + } + } + sort.SliceStable(fileTimingMatches, func(i, j int) bool { + if fileTimingMatches[i].Duration() == fileTimingMatches[j].Duration() { + return fileTimingMatches[i].ClientFilepath > fileTimingMatches[j].ClientFilepath + } + + return fileTimingMatches[i].Duration() > fileTimingMatches[j].Duration() + }) + + if len(fileTimingMatches) == 0 { + s.Log.Warnln("No test file timings were matched. Using naive round-robin strategy.") + } + } + + partitions := make([]testing.TestPartition, 0) + var totalRuntime time.Duration + for _, fileTimingMatch := range fileTimingMatches { + totalRuntime += fileTimingMatch.Duration() + } + partitionRuntime := totalRuntime / time.Duration(cfg.PartitionNodes.Total) + + s.Log.Debugf("Total Runtime: %s", totalRuntime) + s.Log.Debugf("Target Partition Runtime: %s", partitionRuntime) + + for i := 0; i < cfg.PartitionNodes.Total; i++ { + partitions = append(partitions, testing.TestPartition{ + Index: i, + TestFilePaths: make([]string, 0), + Runtime: time.Duration(0), + }) + } + + for _, fileTimingMatch := range fileTimingMatches { + partition := partitionWithLeastRuntime(partitions).Add(fileTimingMatch) + partitions[partition.Index] = partition + s.Log.Debugf("%s: Assigned %s using least runtime strategy", partition, fileTimingMatch) + } + + for i, testFilepath := range unmatchedFilepaths { + partition := partitions[i%len(partitions)] + partitions[partition.Index] = partition.AddFilePath(testFilepath) + s.Log.Debugf("%s: Assigned '%s' using round robin strategy", partition, testFilepath) + } + + return PartitionResult{ + partition: partitions[cfg.PartitionNodes.Index], + utilizedPartitionCount: utilizedPartitionCount(partitions), + }, nil +} + +func partitionWithLeastRuntime(partitions []testing.TestPartition) testing.TestPartition { + selected := partitions[0] + + for _, candidate := range partitions { + if candidate.Runtime < selected.Runtime { + selected = candidate + continue + } + + if candidate.Runtime == selected.Runtime && len(candidate.TestFilePaths) < len(selected.TestFilePaths) { + selected = candidate + } + } + + return selected +} + +func utilizedPartitionCount(partitions []testing.TestPartition) int { + count := 0 + for _, partition := range partitions { + if len(partition.TestFilePaths) != 0 { + count++ + } + } + return count +} + +type PartitionResult struct { + partition testing.TestPartition + utilizedPartitionCount int +} diff --git a/internal/captain/cli/partition_test.go b/internal/captain/cli/partition_test.go new file mode 100644 index 0000000..1feb048 --- /dev/null +++ b/internal/captain/cli/partition_test.go @@ -0,0 +1,736 @@ +package cli_test + +import ( + "context" + "path/filepath" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" + "go.uber.org/zap/zaptest/observer" + + "github.com/rwx-cloud/cli/internal/captain/cli" + "github.com/rwx-cloud/cli/internal/captain/config" + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/mocks" + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func cfgWithArgs( + index int, + total int, + args []string, + delimiter string, + roundRobin bool, + trimPrefix string, +) cli.PartitionConfig { + return cli.PartitionConfig{ + TestFilePaths: args, + PartitionNodes: config.PartitionNodes{ + Index: index, + Total: total, + }, + SuiteID: "captain-cli-test", + Delimiter: delimiter, + RoundRobin: roundRobin, + TrimPrefix: trimPrefix, + } +} + +func cfgWithGlob(index int, total int, glob string) cli.PartitionConfig { + return cfgWithArgs(index, total, []string{glob}, " ", false, "") +} + +func cfgWithGlobAndRoundRobin(index int, total int, glob string) cli.PartitionConfig { + return cfgWithArgs(index, total, []string{glob}, " ", true, "") +} + +var _ = Describe("Partition", func() { + var ( + err error + ctx context.Context + service cli.Service + core zapcore.Core + recordedLogs *observer.ObservedLogs + + fetchedTimingManifest bool + ) + + BeforeEach(func() { + err = nil + fetchedTimingManifest = false + + core, recordedLogs = observer.New(zapcore.DebugLevel) + service = cli.Service{ + API: new(mocks.API), + Log: zaptest.NewLogger(GinkgoT(), zaptest.WrapOptions( + zap.WrapCore(func(_ zapcore.Core) zapcore.Core { return core }), + )).Sugar(), + FileSystem: new(mocks.FileSystem), + TaskRunner: new(mocks.TaskRunner), + ParseConfig: parsing.Config{}, + } + }) + + Context("when misconfigured", func() { + It("requires an index be >= 0", func() { + err = service.Partition(ctx, cfgWithGlob(-1, 2, "*.test")) + Expect(err.Error()).To(ContainSubstring("Missing partition index")) + }) + + It("requires an index be < total", func() { + err = service.Partition(ctx, cfgWithGlob(1, 1, "*.test")) + Expect(err.Error()).To(ContainSubstring("Unsupported partitioning setup")) + err = service.Partition(ctx, cfgWithGlob(2, 1, "*.test")) + Expect(err.Error()).To(ContainSubstring("Unsupported partitioning setup")) + }) + + It("must specify filepath args", func() { + err = service.Partition(ctx, cfgWithArgs(0, 1, []string{}, " ", false, "")) + Expect(err.Error()).To(ContainSubstring("Missing test file paths")) + }) + }) + + Context("when the client provides multiple globs", func() { + BeforeEach(func() { + mockGlob := func(_ string) ([]string, error) { + return []string{"a.test", "b.test", "c.test", "d.test"}, nil + } + mockGetTimingManifest := func(_ context.Context, _ string) ([]testing.TestFileTiming, error) { + return []testing.TestFileTiming{}, nil + } + service.API.(*mocks.API).MockGetTestTimingManifest = mockGetTimingManifest + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("only considers unique filepaths", func() { + _ = service.Partition(ctx, cfgWithArgs(0, 1, []string{"*.test", "*.test"}, " ", false, "")) + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("a.test b.test c.test d.test")) + }) + }) + + Context("under expected conditions", func() { + BeforeEach(func() { + mockGetTimingManifest := func( + _ context.Context, + _ string, + ) ([]testing.TestFileTiming, error) { + fetchedTimingManifest = true + return []testing.TestFileTiming{ + {Filepath: "a.test", Duration: 4}, + {Filepath: "b.test", Duration: 3}, + {Filepath: "c.test", Duration: 2}, + {Filepath: "d.test", Duration: 1}, + }, nil + } + service.API.(*mocks.API).MockGetTestTimingManifest = mockGetTimingManifest + mockGlob := func(_ string) ([]string, error) { + return []string{"a.test", "b.test", "c.test", "d.test"}, nil + } + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("doesnt return an error", func() { + err = service.Partition(ctx, cfgWithGlob(0, 2, "*.test")) + Expect(err).ToNot(HaveOccurred()) + }) + + It("fetches the timing manifest", func() { + _ = service.Partition(ctx, cfgWithGlob(0, 2, "*.test")) + Expect(fetchedTimingManifest).To(BeTrue()) + }) + + It("uses least runtime strategy", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + + assignments := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.DebugLevel).All() { + assignments = append(assignments, log.Message) + } + Expect(assignments).To(ContainElements([]string{ + "Total Runtime: 10ns", + "Target Partition Runtime: 5ns", + "[PART 0 (0.00s)]: Assigned 'a.test' (4ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'b.test' (3ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'c.test' (2ns) using least runtime strategy", + "[PART 0 (0.00s)]: Assigned 'd.test' (1ns) using least runtime strategy", + })) + }) + + It("logs the partitioned files for index 0", func() { + _ = service.Partition(ctx, cfgWithGlob(0, 2, "*.test")) + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("a.test d.test")) + }) + + It("logs the partitioned files for index 1", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("b.test c.test")) + }) + }) + + Context("when round robin is specified", func() { + BeforeEach(func() { + mockGlob := func(_ string) ([]string, error) { + return []string{"a.test", "b.test", "c.test", "d.test"}, nil + } + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("doesnt return an error", func() { + err = service.Partition(ctx, cfgWithGlobAndRoundRobin(0, 2, "*.test")) + Expect(err).ToNot(HaveOccurred()) + }) + + It("uses round-robin strategy", func() { + _ = service.Partition(ctx, cfgWithGlobAndRoundRobin(1, 2, "*.test")) + + assignments := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.DebugLevel).All() { + assignments = append(assignments, log.Message) + } + Expect(assignments).To(Equal([]string{ + "Total Runtime: 0s", + "Target Partition Runtime: 0s", + "[PART 0 (0.00s)]: Assigned 'a.test' using round robin strategy", + "[PART 1 (0.00s)]: Assigned 'b.test' using round robin strategy", + "[PART 0 (0.00s)]: Assigned 'c.test' using round robin strategy", + "[PART 1 (0.00s)]: Assigned 'd.test' using round robin strategy", + })) + }) + + It("logs files for partition 0", func() { + _ = service.Partition(ctx, cfgWithGlobAndRoundRobin(0, 2, "*.test")) + + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("a.test c.test")) + }) + + It("logs files for partition 1", func() { + _ = service.Partition(ctx, cfgWithGlobAndRoundRobin(1, 2, "*.test")) + + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("b.test d.test")) + }) + }) + + Context("when there are no test file timings", func() { + BeforeEach(func() { + mockGlob := func(_ string) ([]string, error) { + return []string{"a.test", "b.test", "c.test", "d.test"}, nil + } + mockGetTimingManifest := func( + _ context.Context, + _ string, + ) ([]testing.TestFileTiming, error) { + fetchedTimingManifest = true + return []testing.TestFileTiming{}, nil + } + service.API.(*mocks.API).MockGetTestTimingManifest = mockGetTimingManifest + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("prints warning to standard err", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + + assignments := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.WarnLevel).All() { + assignments = append(assignments, log.Message) + } + Expect(assignments).To( + ContainElement("No test file timings were matched. Using naive round-robin strategy."), + ) + }) + + It("uses round-robin strategy", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + + assignments := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.DebugLevel).All() { + assignments = append(assignments, log.Message) + } + Expect(assignments).To(Equal([]string{ + "Total Runtime: 0s", + "Target Partition Runtime: 0s", + "[PART 0 (0.00s)]: Assigned 'a.test' using round robin strategy", + "[PART 1 (0.00s)]: Assigned 'b.test' using round robin strategy", + "[PART 0 (0.00s)]: Assigned 'c.test' using round robin strategy", + "[PART 1 (0.00s)]: Assigned 'd.test' using round robin strategy", + })) + }) + + It("logs files for partition 0", func() { + _ = service.Partition(ctx, cfgWithGlob(0, 2, "*.test")) + + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("a.test c.test")) + }) + + It("logs files for partition 1", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("b.test d.test")) + }) + }) + + Context("when test file timings overflow partitions", func() { + BeforeEach(func() { + mockGlob := func(_ string) ([]string, error) { + return []string{"a.test", "b.test", "c.test", "d.test"}, nil + } + mockGetTimingManifest := func( + _ context.Context, + _ string, + ) ([]testing.TestFileTiming, error) { + fetchedTimingManifest = true + return []testing.TestFileTiming{ + {Filepath: "a.test", Duration: 5}, + {Filepath: "b.test", Duration: 4}, + {Filepath: "c.test", Duration: 3}, + {Filepath: "d.test", Duration: 1}, + }, nil + } + service.API.(*mocks.API).MockGetTestTimingManifest = mockGetTimingManifest + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("doesnt return an error", func() { + err = service.Partition(ctx, cfgWithGlob(0, 2, "*.test")) + Expect(err).ToNot(HaveOccurred()) + }) + + It("fetches the timing manifest", func() { + _ = service.Partition(ctx, cfgWithGlob(0, 2, "*.test")) + Expect(fetchedTimingManifest).To(BeTrue()) + }) + + It("uses least runtime strategy, falling back to most remaining when it can't fit", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + + assignments := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.DebugLevel).All() { + assignments = append(assignments, log.Message) + } + Expect(assignments).To(ContainElements([]string{ + "Total Runtime: 13ns", + "Target Partition Runtime: 6ns", + "[PART 0 (0.00s)]: Assigned 'a.test' (5ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'b.test' (4ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'c.test' (3ns) using least runtime strategy", + "[PART 0 (0.00s)]: Assigned 'd.test' (1ns) using least runtime strategy", + })) + }) + + It("logs the partitioned files for index 0", func() { + _ = service.Partition(ctx, cfgWithGlob(0, 2, "*.test")) + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("a.test d.test")) + }) + + It("logs the partitioned files for index 1", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("b.test c.test")) + }) + }) + + Context("when suite run with some timed and some untimed files", func() { + BeforeEach(func() { + mockGlob := func(_ string) ([]string, error) { + return []string{"a.test", "b.test", "c.test", "d.test"}, nil + } + mockGetTimingManifest := func( + _ context.Context, + _ string, + ) ([]testing.TestFileTiming, error) { + fetchedTimingManifest = true + return []testing.TestFileTiming{ + {Filepath: "a.test", Duration: 6}, + {Filepath: "b.test", Duration: 4}, + {Filepath: "c.test", Duration: 3}, + }, nil + } + service.API.(*mocks.API).MockGetTestTimingManifest = mockGetTimingManifest + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("doesnt return an error", func() { + err = service.Partition(ctx, cfgWithGlob(0, 2, "*.test")) + Expect(err).ToNot(HaveOccurred()) + }) + + It("fetches the timing manifest", func() { + _ = service.Partition(ctx, cfgWithGlob(0, 2, "*.test")) + Expect(fetchedTimingManifest).To(BeTrue()) + }) + + It("uses first fit, falls back to most remaining, then round robins unknowns", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + + assignments := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.DebugLevel).All() { + assignments = append(assignments, log.Message) + } + Expect(assignments).To(ContainElements([]string{ + "Total Runtime: 13ns", + "Target Partition Runtime: 6ns", + "[PART 0 (0.00s)]: Assigned 'a.test' (6ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'b.test' (4ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'c.test' (3ns) using least runtime strategy", + "[PART 0 (0.00s)]: Assigned 'd.test' using round robin strategy", + })) + }) + + It("logs the partitioned files for index 0", func() { + _ = service.Partition(ctx, cfgWithGlob(0, 2, "*.test")) + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("a.test d.test")) + }) + + It("logs the partitioned files for index 1", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("b.test c.test")) + }) + }) + + Context("when we're provided out of order timings", func() { + BeforeEach(func() { + mockGlob := func(_ string) ([]string, error) { + return []string{"a.test", "b.test", "c.test", "d.test"}, nil + } + mockGetTimingManifest := func( + _ context.Context, + _ string, + ) ([]testing.TestFileTiming, error) { + fetchedTimingManifest = true + return []testing.TestFileTiming{ + {Filepath: "a.test", Duration: 1}, + {Filepath: "b.test", Duration: 2}, + {Filepath: "c.test", Duration: 3}, + {Filepath: "d.test", Duration: 4}, + }, nil + } + service.API.(*mocks.API).MockGetTestTimingManifest = mockGetTimingManifest + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("sorts and processes in decreasing order", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + + assignments := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.DebugLevel).All() { + assignments = append(assignments, log.Message) + } + Expect(assignments).To(ContainElements([]string{ + "Total Runtime: 10ns", + "Target Partition Runtime: 5ns", + "[PART 0 (0.00s)]: Assigned 'd.test' (4ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'c.test' (3ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'b.test' (2ns) using least runtime strategy", + "[PART 0 (0.00s)]: Assigned 'a.test' (1ns) using least runtime strategy", + })) + }) + }) + + Context("when we have moar partitions", func() { + BeforeEach(func() { + mockGlob := func(_ string) ([]string, error) { + return []string{"a.test", "b.test", "c.test", "d.test"}, nil + } + mockGetTimingManifest := func( + _ context.Context, + _ string, + ) ([]testing.TestFileTiming, error) { + fetchedTimingManifest = true + return []testing.TestFileTiming{ + {Filepath: "d.test", Duration: 1}, + {Filepath: "c.test", Duration: 2}, + {Filepath: "b.test", Duration: 3}, + {Filepath: "a.test", Duration: 4}, + }, nil + } + service.API.(*mocks.API).MockGetTestTimingManifest = mockGetTimingManifest + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("utilizes them best it can", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 3, "*.test")) + + assignments := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.DebugLevel).All() { + assignments = append(assignments, log.Message) + } + Expect(assignments).To(ContainElements([]string{ + "Total Runtime: 10ns", + "Target Partition Runtime: 3ns", + "[PART 0 (0.00s)]: Assigned 'a.test' (4ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'b.test' (3ns) using least runtime strategy", + "[PART 2 (0.00s)]: Assigned 'c.test' (2ns) using least runtime strategy", + "[PART 2 (0.00s)]: Assigned 'd.test' (1ns) using least runtime strategy", + })) + }) + }) + + Context("when the server sends down ./filepaths", func() { + BeforeEach(func() { + mockGlob := func(_ string) ([]string, error) { + return []string{"a.test", "b.test", "c.test", "d.test"}, nil + } + mockGetTimingManifest := func( + _ context.Context, + _ string, + ) ([]testing.TestFileTiming, error) { + fetchedTimingManifest = true + return []testing.TestFileTiming{ + {Filepath: "./d.test", Duration: 1}, + {Filepath: "./c.test", Duration: 2}, + {Filepath: "./b.test", Duration: 3}, + {Filepath: "./a.test", Duration: 4}, + }, nil + } + service.API.(*mocks.API).MockGetTestTimingManifest = mockGetTimingManifest + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("still matches because we are comparing expanded absolute paths", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + + assignments := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.DebugLevel).All() { + assignments = append(assignments, log.Message) + } + Expect(assignments).To(ContainElements([]string{ + "Total Runtime: 10ns", + "Target Partition Runtime: 5ns", + "[PART 0 (0.00s)]: Assigned 'a.test' (4ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'b.test' (3ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'c.test' (2ns) using least runtime strategy", + "[PART 0 (0.00s)]: Assigned 'd.test' (1ns) using least runtime strategy", + })) + }) + }) + + Context("when the server sends down fully expanded paths", func() { + BeforeEach(func() { + mockGlob := func(_ string) ([]string, error) { + return []string{"a.test", "b.test", "c.test", "d.test"}, nil + } + mockGetTimingManifest := func( + _ context.Context, + _ string, + ) ([]testing.TestFileTiming, error) { + fetchedTimingManifest = true + a, _ := filepath.Abs("a.test") + b, _ := filepath.Abs("b.test") + c, _ := filepath.Abs("c.test") + d, _ := filepath.Abs("d.test") + return []testing.TestFileTiming{ + {Filepath: a, Duration: 4}, + {Filepath: b, Duration: 3}, + {Filepath: c, Duration: 2}, + {Filepath: d, Duration: 1}, + }, nil + } + service.API.(*mocks.API).MockGetTestTimingManifest = mockGetTimingManifest + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("still matches because we are comparing expanded absolute paths", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + + assignments := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.DebugLevel).All() { + assignments = append(assignments, log.Message) + } + Expect(assignments).To(ContainElements( + "Total Runtime: 10ns", + "Target Partition Runtime: 5ns", + "[PART 0 (0.00s)]: Assigned 'a.test' (4ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'b.test' (3ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'c.test' (2ns) using least runtime strategy", + "[PART 0 (0.00s)]: Assigned 'd.test' (1ns) using least runtime strategy", + )) + }) + + It("logs the partitioned files for index 0 using client test file paths", func() { + _ = service.Partition(ctx, cfgWithGlob(0, 2, "*.test")) + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("a.test d.test")) + }) + + It("logs the partitioned files for index 1 using client test file paths", func() { + _ = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("b.test c.test")) + }) + }) + + Context("when the server returns an error", func() { + BeforeEach(func() { + mockGlob := func(_ string) ([]string, error) { + return []string{"a.test", "b.test", "c.test", "d.test"}, nil + } + mockGetTimingManifest := func( + _ context.Context, + _ string, + ) ([]testing.TestFileTiming, error) { + return nil, errors.NewSystemError("something bad!") + } + service.API.(*mocks.API).MockGetTestTimingManifest = mockGetTimingManifest + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("raises an error because partitioning one index and round-robining another is problematic ", func() { + err = service.Partition(ctx, cfgWithGlob(1, 2, "*.test")) + Expect(err.Error()).To(ContainSubstring("something bad!")) + }) + }) + + Context("when using a custom delimiter", func() { + BeforeEach(func() { + mockGlob := func(_ string) ([]string, error) { + return []string{"a.test", "b.test", "c.test", "d.test"}, nil + } + mockGetTimingManifest := func( + _ context.Context, + _ string, + ) ([]testing.TestFileTiming, error) { + fetchedTimingManifest = true + a, _ := filepath.Abs("a.test") + b, _ := filepath.Abs("b.test") + c, _ := filepath.Abs("c.test") + d, _ := filepath.Abs("d.test") + return []testing.TestFileTiming{ + {Filepath: a, Duration: 4}, + {Filepath: b, Duration: 3}, + {Filepath: c, Duration: 2}, + {Filepath: d, Duration: 1}, + }, nil + } + service.API.(*mocks.API).MockGetTestTimingManifest = mockGetTimingManifest + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("raises an error because partitioning one index and round-robining another is problematic ", func() { + _ = service.Partition(ctx, cfgWithArgs(0, 1, []string{"*.test"}, ",", false, "")) + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("a.test,b.test,c.test,d.test")) + }) + }) + + Context("when using an omit prefix", func() { + BeforeEach(func() { + mockGlob := func(_ string) ([]string, error) { + return []string{"test/a.test", "test/b.test", "test/c.test", "test/d.test"}, nil + } + mockGetTimingManifest := func( + _ context.Context, + _ string, + ) ([]testing.TestFileTiming, error) { + fetchedTimingManifest = true + a, _ := filepath.Abs("a.test") + b, _ := filepath.Abs("b.test") + c, _ := filepath.Abs("c.test") + d, _ := filepath.Abs("d.test") + return []testing.TestFileTiming{ + {Filepath: a, Duration: 4}, + {Filepath: b, Duration: 3}, + {Filepath: c, Duration: 2}, + {Filepath: d, Duration: 1}, + }, nil + } + service.API.(*mocks.API).MockGetTestTimingManifest = mockGetTimingManifest + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("still matches because it first trims the trim-prefix", func() { + globConfig := cfgWithArgs(1, 2, []string{"tests/*.test"}, " ", false, "test/") + _ = service.Partition(ctx, globConfig) + + assignments := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.DebugLevel).All() { + assignments = append(assignments, log.Message) + } + Expect(assignments).To(ContainElements( + "Trimming prefix 'test/' from 'test/a.test' resulting in 'a.test' for comparison", + "Trimming prefix 'test/' from 'test/b.test' resulting in 'b.test' for comparison", + "Trimming prefix 'test/' from 'test/c.test' resulting in 'c.test' for comparison", + "Trimming prefix 'test/' from 'test/d.test' resulting in 'd.test' for comparison", + "Total Runtime: 10ns", + "Target Partition Runtime: 5ns", + "[PART 0 (0.00s)]: Assigned 'test/a.test' (4ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'test/b.test' (3ns) using least runtime strategy", + "[PART 1 (0.00s)]: Assigned 'test/c.test' (2ns) using least runtime strategy", + "[PART 0 (0.00s)]: Assigned 'test/d.test' (1ns) using least runtime strategy", + )) + }) + + It("logs the partitioned files for index 0 using client test file paths", func() { + _ = service.Partition(ctx, cfgWithArgs(0, 2, []string{"tests/*.test"}, " ", false, "test/")) + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("test/a.test test/d.test")) + }) + + It("logs the partitioned files for index 1 using client test file paths", func() { + _ = service.Partition(ctx, cfgWithArgs(1, 2, []string{"tests/*.test"}, " ", false, "test/")) + logMessages := make([]string, 0) + for _, log := range recordedLogs.FilterLevelExact(zap.InfoLevel).All() { + logMessages = append(logMessages, log.Message) + } + Expect(logMessages).To(ContainElement("test/b.test test/c.test")) + }) + }) +}) diff --git a/internal/captain/cli/run.go b/internal/captain/cli/run.go new file mode 100644 index 0000000..0971783 --- /dev/null +++ b/internal/captain/cli/run.go @@ -0,0 +1,1035 @@ +package cli + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/mattn/go-shellwords" + "golang.org/x/sync/errgroup" + + "github.com/rwx-cloud/cli/internal/captain/backend" + "github.com/rwx-cloud/cli/internal/captain/backend/remote" + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/exec" + "github.com/rwx-cloud/cli/internal/captain/mint" + "github.com/rwx-cloud/cli/internal/captain/reporting" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +// RunSuite runs the specified build- or test-suite and optionally uploads the resulting test results file. +func (s Service) RunSuite(ctx context.Context, cfg RunConfig) (finalErr error) { + err := cfg.Validate(s.Log) + if err != nil { + return errors.WithStack(err) + } + + // Fetch run configuration in the background + var apiConfiguration backend.RunConfiguration + eg, egCtx := errgroup.WithContext(ctx) + eg.Go(func() error { + apiConfiguration, err = s.API.GetRunConfiguration(egCtx, cfg.SuiteID) + if err != nil { + return errors.WithStack(err) + } + + if len(apiConfiguration.QuarantinedTests) == 0 { + s.Log.Debug("No quarantined tests defined") + } + + cfg.CloudOrganizationSlug = apiConfiguration.OrganizationSlug + + return nil + }) + + stdout := os.Stdout + if cfg.Quiet { + // According to the documentation, passing in a nil pointer to `os.Exec` + // should also work - however, that seems to open /dev/null with the + // wrong flags, leading to errors like + // `cat: standard output: Bad file descriptor` + stdout, err = os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, 0o666) + if err != nil { + s.Log.Warnf("Could not open %s for writing", os.DevNull) + } + } + + var runErr error + var testResults *v1.TestResults + var newlyExecutedTestResults *v1.TestResults + var testResultsFiles []string + + startingRetryID := 0 + lastRetryID := 0 + if cfg.DidRetryFailedTestsInMint { + testResults, err = mint.ReadFailedTestResults(s.FileSystem) + if err != nil { + return errors.Wrap(err, "Could not load the failed tests from the previous attempt") + } + previousLastRetryIDValue, ok := testResults.Meta["last_retry_id"] + if !ok { + return errors.NewInternalError("Could not detect last retry ID in previous test results") + } + + // JSON numbers are parsed as float64, so we need to handle both int and float64 + switch v := previousLastRetryIDValue.(type) { + case int: + startingRetryID = v + case float64: + startingRetryID = int(v) + default: + return errors.NewInternalError( + "Last retry ID in previous test results is not a number: %v (type: %T)", + previousLastRetryIDValue, + previousLastRetryIDValue, + ) + } + + err = mint.RestoreIntermediateArtifacts(s.FileSystem, cfg.IntermediateArtifactsPath) + if err != nil { + return errors.Wrap(err, "Could not restore intermediate artifacts from the previous attempt") + } + + if testResults.Summary.OtherErrors > 0 { + errorMessage := fmt.Sprintf( + "The previous execution of this test suite had errors that occurred outside the test suite. "+ + "These errors cannot be retried individually. Use 'Retry' instead of %q.", + mint.RetryFailedTestsLabel(), + ) + _ = mint.WriteError(s.FileSystem, errorMessage) + return errors.NewInputError("%s", errorMessage) + } + + hasFailures := false + + for _, test := range testResults.Tests { + if test.Attempt.Status.ImpliesFailure() { + hasFailures = true + break + } + } + + if !hasFailures { + if os.Getenv("RWX_TEST_RETRY_FAILED_TESTS_PASS_ON_NO_TESTS") == "true" || os.Getenv("CAPTAIN_RETRY_FAILED_TESTS_PASS_ON_NO_TESTS") == "true" { + s.Log.Infoln("No tests to retry detected") + newlyExecutedTestResults = v1.NewTestResults(testResults.Framework, []v1.Test{}, []v1.OtherError{}) + } else { + return errors.NewRetryError("No tests to retry detected") + } + } else { + // Wait until run configuration was fetched. Ignore any errors. + if err := eg.Wait(); err != nil { + s.Log.Warnf("Unable to fetch run configuration from RWX: %s", err) + } + + cfg.Retries++ + if cfg.Retries < 1 { + cfg.Retries = 1 + } + cfg.FlakyRetries++ + if cfg.FlakyRetries < 1 { + cfg.FlakyRetries = 1 + } + + // start with runErr in an error state to represent the error from the previous task attempt + runErr = errors.NewExecutionError(1, "test suite had failed tests") + + newlyExecutedTestResults = v1.NewTestResults(testResults.Framework, []v1.Test{}, []v1.OtherError{}) + testResults, newlyExecutedTestResults, lastRetryID, err = s.attemptRetries( + ctx, + testResults, + newlyExecutedTestResults, + cfg, + apiConfiguration, + startingRetryID, + ) + if err != nil { + if _, ok := errors.AsRetryError(err); ok { + return errors.WithStack(err) + } + s.Log.Warnf("An issue occurred while retrying your tests: %v", err) + } + } + } else { + runCommand, err := s.makeRunCommand(ctx, cfg) + if err != nil { + return errors.Wrapf(err, "Failed to assemble run command") + } + + // Short circuit and print warning info (e.g attempting to run an empty partition) + if runCommand.shortCircuit { + s.Log.Warnf(runCommand.shortCircuitInfo) + os.Exit(0) + } + + // Run sub-command + ctx, cmdErr := s.runCommand(ctx, runCommand.commandArgs, stdout, []string{}) + testResults, testResultsFiles, runErr, err = s.handleCommandOutcome(cfg, cmdErr, lastRetryID) + if err != nil { + return err + } + + if testResults != nil { + tests := make([]v1.Test, len(testResults.Tests)) + copy(tests, testResults.Tests) + + otherErrors := make([]v1.OtherError, len(testResults.OtherErrors)) + copy(otherErrors, testResults.OtherErrors) + + derivedFrom := make([]v1.OriginalTestResults, len(testResults.DerivedFrom)) + copy(derivedFrom, testResults.DerivedFrom) + + newlyExecutedTestResults = v1.NewTestResults(testResults.Framework, tests, otherErrors) + newlyExecutedTestResults.DerivedFrom = derivedFrom + } + + // Wait until run configuration was fetched. Ignore any errors. + if err := eg.Wait(); err != nil { + s.Log.Warnf("Unable to fetch run configuration from RWX: %s", err) + } + + // Always move artifacts to IAS after original attempt when intermediate artifacts path is configured + if testResults != nil && len(testResultsFiles) > 0 { + ias, err := s.NewIntermediateArtifactStorage(cfg.IntermediateArtifactsPath) + if err != nil { + return errors.WithStack(err) + } + + // Original attempt uses default "original-attempt" retryID + if err := ias.moveTestResults(testResultsFiles); err != nil { + return errors.WithStack(err) + } + if err := ias.MoveAdditionalArtifacts(cfg.AdditionalArtifactPaths); err != nil { + return errors.WithStack(err) + } + } + + testResults, newlyExecutedTestResults, lastRetryID, err = s.attemptRetries( + ctx, + testResults, + newlyExecutedTestResults, + cfg, + apiConfiguration, + 0, + ) + if err != nil { + if _, ok := errors.AsRetryError(err); ok { + return errors.WithStack(err) + } + s.Log.Warnf("An issue occurred while retrying your tests: %v", err) + } + } + + quarantinedFailedTests := make([]v1.Test, 0) + unquarantinedFailedTests := make([]v1.Test, 0) + otherErrorCount := 0 + + quarantinedTests, err := s.API.GetQuarantinedTests(ctx, cfg.SuiteID) + if err != nil { + // Fallback to quarantined tests from run configuration + quarantinedTests = make([]backend.Test, len(apiConfiguration.QuarantinedTests)) + for i, qt := range apiConfiguration.QuarantinedTests { + quarantinedTests[i] = qt.Test + } + } + + if testResults != nil { + otherErrorCount = testResults.Summary.OtherErrors + + for i, test := range testResults.Tests { + if s.isIdentifiedIn(test, quarantinedTests) && test.Attempt.Status.PotentiallyFlaky() { + testResults.Tests[i] = test.Quarantine() + s.Log.Debugf("quarantined %v test: %v", test.Attempt.Status, test) + quarantinedFailedTests = append(quarantinedFailedTests, test) + } else if test.Attempt.Status.ImpliesFailure() { + s.Log.Debugf("did not quarantine %v test: %v", test.Attempt.Status, test) + unquarantinedFailedTests = append(unquarantinedFailedTests, test) + } + } + testResults.Summary = v1.NewSummary(testResults.Tests, testResults.OtherErrors) + if testResults.Meta == nil { + testResults.Meta = map[string]any{} + } + testResults.Meta["captain_suite_id"] = cfg.SuiteID + testResults.Meta["last_retry_id"] = lastRetryID + } + + if newlyExecutedTestResults != nil { + for i, test := range newlyExecutedTestResults.Tests { + if s.isIdentifiedIn(test, quarantinedTests) && test.Attempt.Status.PotentiallyFlaky() { + newlyExecutedTestResults.Tests[i] = test.Quarantine() + } + } + newlyExecutedTestResults.Summary = v1.NewSummary(newlyExecutedTestResults.Tests, newlyExecutedTestResults.OtherErrors) + if newlyExecutedTestResults.Meta == nil { + newlyExecutedTestResults.Meta = map[string]any{} + } + newlyExecutedTestResults.Meta["captain_suite_id"] = cfg.SuiteID + } + + if newlyExecutedTestResults != nil { + otelSpanAttributes := rwxOtelSpanAttributes( + *newlyExecutedTestResults, + cfg.SuiteID, + ) + if writeErr := mint.WriteOtelSpanAttributesJSON(s.FileSystem, s.Log, otelSpanAttributes); writeErr != nil { + s.Log.Warnf("Unable to write RWX OTEL span attributes: %s", writeErr.Error()) + } + } + + var uploadResults []backend.TestResultsUploadResult + var uploadError error + var headerPrinted bool + + if cfg.PrintSummary { + s.printHeader() + headerPrinted = true + } + + // We ignore the error here since `UploadTestResults` will already log any errors. Furthermore, any errors here will + // not affect the exit code. + if testResults != nil { + uploadResults, uploadError = s.reportTestResults(ctx, cfg, *testResults, *newlyExecutedTestResults) + } else { + s.Log.Debugf("No test results were parsed. Globbed files: %v", testResultsFiles) + } + + // Display detailed output if necessary + hasUploadResults := len(uploadResults) > 0 + hasQuarantinedFailedTests := len(quarantinedFailedTests) > 0 + hasDetails := hasUploadResults || hasQuarantinedFailedTests + + if hasDetails && !headerPrinted && !cfg.Quiet { + s.printHeader() + headerPrinted = true + } + + if hasUploadResults && headerPrinted { + uploadedPaths := make([]string, 0) + erroredPaths := make([]string, 0) + for _, uploadResult := range uploadResults { + if uploadResult.Uploaded { + uploadedPaths = append(uploadedPaths, uploadResult.OriginalPaths...) + } else { + erroredPaths = append(erroredPaths, uploadResult.OriginalPaths...) + } + } + testResultsFilesFound := len(uploadedPaths) + len(erroredPaths) + + s.Log.Infoln( + fmt.Sprintf( + "\nFound %v test result %v:", + testResultsFilesFound, + pluralize(testResultsFilesFound, "file", "files"), + ), + ) + + for _, uploadedPath := range uploadedPaths { + s.Log.Infoln(fmt.Sprintf("- Updated RWX with results from %v", uploadedPath)) + } + for _, erroredPath := range erroredPaths { + s.Log.Infoln(fmt.Sprintf("- Unable to update RWX with results from %v", erroredPath)) + } + } + + // This section is always printed, even when `--quiet` is specified + if hasQuarantinedFailedTests { + s.Log.Infoln( + fmt.Sprintf( + "\n%v of %v %v under quarantine:", + len(quarantinedFailedTests), + len(quarantinedFailedTests)+len(unquarantinedFailedTests), + pluralize(len(quarantinedFailedTests)+len(unquarantinedFailedTests), "failure", "failures"), + ), + ) + + for _, quarantinedFailedTest := range quarantinedFailedTests { + s.Log.Infoln(fmt.Sprintf("- %v", quarantinedFailedTest.Name)) + } + } + + if hasDetails && hasQuarantinedFailedTests && len(unquarantinedFailedTests) > 0 { + s.Log.Infoln( + fmt.Sprintf( + "\n%v remaining actionable %v", + len(unquarantinedFailedTests), + pluralize(len(unquarantinedFailedTests), "failure", "failures"), + ), + ) + + for _, unquarantinedFailedTests := range unquarantinedFailedTests { + s.Log.Infoln(fmt.Sprintf("- %v", unquarantinedFailedTests.Name)) + } + } + + if hasDetails && hasQuarantinedFailedTests && otherErrorCount > 0 { + s.Log.Infoln( + fmt.Sprintf( + "\n%v other %v occurred", + otherErrorCount, + pluralize(otherErrorCount, "failure", "failures"), + ), + ) + } + + // Return the original exit code if there was a non-test error + if runErr != nil && otherErrorCount > 0 { + err = errors.WithStack(runErr) + } + + // Return the original exit code if we didn't detect any failures + // (perhaps an error in parsing, failure of the framework to output an artifact, or coverage requirement) + if runErr != nil && len(quarantinedFailedTests)+len(unquarantinedFailedTests) == 0 && startingRetryID == lastRetryID { + err = errors.WithStack(runErr) + } + + // Return the original exit code if we didn't quarantine all of the failures we saw + if runErr != nil && len(unquarantinedFailedTests) > 0 { + err = errors.WithStack(runErr) + } + + if uploadError != nil && cfg.FailOnUploadError { + err = uploadError + } + + if err != nil { + // Fetch a fresh copy of apiConfiguration to see if the suite has been quarantined while it was running + updatedConfiguration, getRunErr := s.API.GetRunConfiguration(ctx, cfg.SuiteID) + if getRunErr != nil { + s.Log.Warnf("Failed to get the updated run configuration, will fall back to the original: %v", getRunErr) + } else { + apiConfiguration = updatedConfiguration + } + + if apiConfiguration.IsSuiteQuarantined { + s.Log.Error(err) + s.Log.Infoln("Exiting with exit code 0 because the test suite is quarantined") + + return nil + } + return err + } + + return nil +} + +func (s Service) attemptRetries( + ctx context.Context, + originalTestResults *v1.TestResults, + newlyExecutedTestResults *v1.TestResults, + cfg RunConfig, + apiConfiguration backend.RunConfiguration, + startingRetryID int, +) (*v1.TestResults, *v1.TestResults, int, error) { + nonFlakyRetries := cfg.Retries + flakyRetries := cfg.FlakyRetries + quarantinedTestRetries := cfg.QuarantinedTestRetries + + if nonFlakyRetries <= 0 && flakyRetries <= 0 { + return originalTestResults, newlyExecutedTestResults, startingRetryID, nil + } + + ias, err := s.NewIntermediateArtifactStorage(cfg.IntermediateArtifactsPath) + if err != nil { + return originalTestResults, newlyExecutedTestResults, startingRetryID, errors.WithStack(err) + } + + if cfg.IntermediateArtifactsPath == "" { + defer func() { + if err := ias.delete(); err != nil { + s.Log.Warnf("Unable to clean up temporary files: %s", err.Error()) + } + }() + } + + // if retries is set and flaky-retries is not, set flaky-retries to retries + // this way, we can isolate the logic for flaky and non-flaky instead of special casing everywhere + // note: it's important we don't do this the other way around; retries implies flaky-retries, flaky-retries + // does not imply retries + if nonFlakyRetries > 0 && flakyRetries < 0 { + flakyRetries = nonFlakyRetries + } + + if originalTestResults == nil { + return originalTestResults, newlyExecutedTestResults, + startingRetryID, errors.NewInternalError("No test results detected") + } + + compiledRetryTemplate, err := templating.CompileTemplate(cfg.RetryCommandTemplate) + if err != nil { + return originalTestResults, newlyExecutedTestResults, startingRetryID, errors.WithStack(err) + } + + framework := originalTestResults.Framework + var substitution targetedretries.Substitution = targetedretries.JSONSubstitution{FileSystem: s.FileSystem} + if err := substitution.ValidateTemplate(compiledRetryTemplate); err != nil { + frameworkSubstitution, ok := cfg.SubstitutionsByFramework[framework] + if !ok { + return originalTestResults, newlyExecutedTestResults, + startingRetryID, errors.NewInternalError("Unable to retry %q", framework) + } + + if err := frameworkSubstitution.ValidateTemplate(compiledRetryTemplate); err != nil { + return originalTestResults, newlyExecutedTestResults, startingRetryID, errors.WithStack(err) + } + + substitution = frameworkSubstitution + } + + maxTestsToRetryCount, err := cfg.MaxTestsToRetryCount() + if err != nil { + return originalTestResults, newlyExecutedTestResults, startingRetryID, errors.WithStack(err) + } + + maxTestsToRetryPercentage, err := cfg.MaxTestsToRetryPercentage() + if err != nil { + return originalTestResults, newlyExecutedTestResults, startingRetryID, errors.WithStack(err) + } + + maxRetries := nonFlakyRetries + if quarantinedTestRetries > maxRetries { + maxRetries = quarantinedTestRetries + } + if flakyRetries > maxRetries { + maxRetries = flakyRetries + } + formattedRetryTotal := fmt.Sprintf(" of %v", maxRetries) + if flakyRetries > 0 && nonFlakyRetries > 0 && nonFlakyRetries != flakyRetries && + quarantinedTestRetries > 0 && quarantinedTestRetries != flakyRetries { + formattedRetryTotal = "" + } + + flattenedTestResults := originalTestResults + flattenedNewlyExecutedTestResults := newlyExecutedTestResults + + retryID := startingRetryID + + for retries := 0; retries < maxRetries; retries++ { + remainingFlakyFailures := make([]v1.Test, 0) + remainingNonFlakyFailures := make([]v1.Test, 0) + remainingQuarantinedTestFailures := make([]v1.Test, 0) + + for _, test := range flattenedTestResults.Tests { + if !test.Attempt.Status.ImpliesFailure() { + continue + } + + if s.isIdentifiedIn(test, apiConfiguration.FlakyTests) { + remainingFlakyFailures = append(remainingFlakyFailures, test) + } else { + remainingNonFlakyFailures = append(remainingNonFlakyFailures, test) + } + } + + nonFlakyAttemptsExhausted := retries >= nonFlakyRetries + flakyAttemptsExhausted := retries >= flakyRetries + quarantinedTestAttemptsExhausted := retries >= quarantinedTestRetries + + testsRemaining := 0 + if !nonFlakyAttemptsExhausted { + testsRemaining += len(remainingNonFlakyFailures) + } + if !flakyAttemptsExhausted { + testsRemaining += len(remainingFlakyFailures) + } + if !quarantinedTestAttemptsExhausted { + testsRemaining += len(remainingQuarantinedTestFailures) + } + + // bail early if there are too many failed tests + if maxTestsToRetryCount != nil && testsRemaining > *maxTestsToRetryCount { + break + } + + // bail early if there are too many failed tests + testCount := float64(flattenedTestResults.Summary.Tests) + if maxTestsToRetryPercentage != nil && + float64(testsRemaining) > testCount**maxTestsToRetryPercentage/100 { + break + } + + // nothing left to retry + if testsRemaining == 0 { + break + } + + // all attempts exhausted + if nonFlakyAttemptsExhausted && flakyAttemptsExhausted { + break + } + + // fail fast if we know we can't pass the build + if cfg.FailRetriesFast && ((nonFlakyAttemptsExhausted && len(remainingNonFlakyFailures) > 0) || + (flakyAttemptsExhausted && len(remainingFlakyFailures) > 0)) { + break + } + + filter := s.CreateRetryFilter(apiConfiguration, remainingFlakyFailures, retries, flakyRetries, + nonFlakyRetries, quarantinedTestRetries) + + retryID++ + ias.SetRetryID(retryID) + + allNewTestResults := make([]v1.TestResults, 0) + allSubstitutions, err := substitution.SubstitutionsFor(compiledRetryTemplate, *flattenedTestResults, filter) + if err != nil { + return flattenedTestResults, flattenedNewlyExecutedTestResults, retryID, errors.Wrap( + err, + "Unable construct retry substitutions", + ) + } + + for i, substitutions := range allSubstitutions { + command := compiledRetryTemplate.Substitute(substitutions) + args, err := shellwords.Parse(command) + if err != nil { + return flattenedTestResults, flattenedNewlyExecutedTestResults, retryID, errors.Wrapf( + err, + "Unable to parse %q into shell arguments", + command, + ) + } + + ias.SetCommandID(i + 1) + env := []string{ + fmt.Sprintf("RWX_TEST_RETRY_ATTEMPT_NUMBER=%v", retries+1), + fmt.Sprintf("RWX_TEST_RETRY_INVOCATION_NUMBER=%v", i+1), + fmt.Sprintf("RWX_TEST_RETRY_COMMAND_ID=%v-%v", retries+1, i+1), + fmt.Sprintf("CAPTAIN_RETRY_ATTEMPT_NUMBER=%v", retries+1), + fmt.Sprintf("CAPTAIN_RETRY_INVOCATION_NUMBER=%v", i+1), + fmt.Sprintf("CAPTAIN_RETRY_COMMAND_ID=%v-%v", retries+1, i+1), + } + + s.Log.Infoln() + s.Log.Infoln(strings.Repeat("-", 80)) + if len(allSubstitutions) == 1 { + s.Log.Infoln(fmt.Sprintf("- Retry %v%v", retries+1, formattedRetryTotal)) + } else { + s.Log.Infoln(fmt.Sprintf( + "- Retry %v%v, command %v of %v", + retries+1, + formattedRetryTotal, + i+1, + len(allSubstitutions), + )) + } + for keyword, value := range substitutions { + s.Log.Infoln(fmt.Sprintf("- %v: %v", keyword, value)) + } + s.Log.Infoln(strings.Repeat("-", 80)) + s.Log.Infoln() + + stdout := os.Stdout + if cfg.Quiet { + stdout, err = os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, 0o666) + if err != nil { + s.Log.Warnf("Could not open %s for writing", os.DevNull) + } + } + + for _, preRetryCommand := range cfg.PreRetryCommands { + preRetryArgs, err := shellwords.Parse(preRetryCommand) + if err != nil { + return flattenedTestResults, flattenedNewlyExecutedTestResults, retryID, errors.Wrapf( + err, + "Unable to parse %q into shell arguments", + preRetryCommand, + ) + } + + if _, err := s.runCommand(ctx, preRetryArgs, stdout, env); err != nil { + return flattenedTestResults, flattenedNewlyExecutedTestResults, retryID, errors.Wrapf( + err, + "Error while executing %q", + preRetryCommand, + ) + } + } + + _, cmdErr := s.runCommand(ctx, args, stdout, env) + + for _, postRetryCommand := range cfg.PostRetryCommands { + postRetryArgs, err := shellwords.Parse(postRetryCommand) + if err != nil { + return flattenedTestResults, flattenedNewlyExecutedTestResults, retryID, errors.Wrapf( + err, + "Unable to parse %q into shell arguments", + postRetryCommand, + ) + } + + if _, err := s.runCommand(ctx, postRetryArgs, stdout, env); err != nil { + return flattenedTestResults, flattenedNewlyExecutedTestResults, retryID, errors.Wrapf( + err, + "Error while executing %q", + postRetryCommand, + ) + } + } + + // +1 because it's 1-indexed + newTestResults, newTestResultsFiles, _, err := s.handleCommandOutcome(cfg, cmdErr, retryID) + if err != nil { + return flattenedTestResults, flattenedNewlyExecutedTestResults, retryID, err + } + + if newTestResults != nil { + allNewTestResults = append(allNewTestResults, *newTestResults) + } + if err := ias.moveTestResults(newTestResultsFiles); err != nil { + return flattenedTestResults, flattenedNewlyExecutedTestResults, retryID, errors.WithStack(err) + } + if err := ias.MoveAdditionalArtifacts(cfg.AdditionalArtifactPaths); err != nil { + return flattenedTestResults, flattenedNewlyExecutedTestResults, retryID, errors.WithStack(err) + } + } + if jsonSubstitution, ok := substitution.(targetedretries.JSONSubstitution); ok { + if err := jsonSubstitution.CleanUp(allSubstitutions); err != nil { + s.Log.Warn(err) + } + } + + FLATTENED_TEST_RESULTS: + for _, originalTest := range flattenedTestResults.Tests { + if !filter(originalTest) { + continue + } + + for _, retriedResult := range allNewTestResults { + for _, retriedTest := range retriedResult.Tests { + if originalTest.Matches(retriedTest) { + continue FLATTENED_TEST_RESULTS + } + } + } + + missingTestResult := fmt.Sprintf( + "The retry command of suite %q appears to be misconfigured. "+ + "rwx test could not identify the original (failed) test in the output of the retry command.", + cfg.SuiteID, + ) + if cfg.FailOnMisconfiguredRetry { + return flattenedTestResults, flattenedNewlyExecutedTestResults, + retryID, errors.NewRetryError("%s", missingTestResult) + } + s.Log.Warn(missingTestResult) + } + + mergedTestResults := v1.Merge([]v1.TestResults{*flattenedTestResults}, allNewTestResults) + flattenedTestResults = &mergedTestResults + + mergedNewlyExecutedTestResults := v1.Merge([]v1.TestResults{*flattenedNewlyExecutedTestResults}, allNewTestResults) + flattenedNewlyExecutedTestResults = &mergedNewlyExecutedTestResults + } + + s.Log.Debugf("Retries complete, summary: %v\n", flattenedTestResults.Summary) + return flattenedTestResults, flattenedNewlyExecutedTestResults, retryID, nil +} + +func (s Service) CreateRetryFilter( + apiConfiguration backend.RunConfiguration, + remainingFlakyFailures []v1.Test, + retries int, + flakyRetries int, + nonFlakyRetries int, + quarantinedTestRetries int, +) func(test v1.Test) bool { + return func(test v1.Test) bool { + if !test.Attempt.Status.ImpliesFailure() { + return false + } + + quarantinedTests := make([]backend.Test, len(apiConfiguration.QuarantinedTests)) + for i, qt := range apiConfiguration.QuarantinedTests { + quarantinedTests[i] = qt.Test + } + testIsQuarantined := s.isIdentifiedIn(test, quarantinedTests) + + // Handle quarantined test retry logic + if testIsQuarantined { + if quarantinedTestRetries == 0 { + s.Log.Debugf("Skipping %v; test is quarantined and quarantined test retries is 0\n", test) + return false + } + if quarantinedTestRetries > 0 && retries >= quarantinedTestRetries { + s.Log.Debugf("Skipping %v; quarantined test attempts exhausted\n", test) + return false + } + } + + testIsFlaky := false + for _, remainingFlakyFailure := range remainingFlakyFailures { + if test.Matches(remainingFlakyFailure) { + testIsFlaky = true + break + } + } + + if retries >= flakyRetries && testIsFlaky { + s.Log.Debugf("Skipping %v; flaky attempts exhausted\n", test) + return false + } + + if retries >= nonFlakyRetries && !testIsFlaky { + s.Log.Debugf("Skipping %v; non-flaky attempts exhausted\n", test) + return false + } + + return true + } +} + +func (s Service) handleCommandOutcome( + cfg RunConfig, + cmdErr error, + retryID int, +) (*v1.TestResults, []string, error, error) { + var runErr error + ok := true + if cmdErr != nil { + runErr, ok = errors.AsExecutionError(cmdErr) + } + if !ok { + return nil, nil, errors.WithStack(runErr), errors.WithStack(cmdErr) + } + + // Return early if no testResultsFiles were defined over the CLI. If there was an error + // during execution, the exit Code is being passed along. + if cfg.TestResultsFileGlob == "" { + s.Log.Debug("No testResultsFile path provided, quitting") + return nil, nil, errors.WithStack(runErr), errors.WithStack(runErr) + } + + testResultsFiles, err := s.FileSystem.Glob(cfg.TestResultsFileGlob) + if err != nil { + return nil, + testResultsFiles, + errors.WithStack(runErr), + errors.NewSystemError("unable to expand filepath glob: %s", err) + } + + // retryID + 1 because the group numbers are 1-indexed + testResults, err := s.parse(testResultsFiles, retryID+1) + if err != nil { + return nil, + testResultsFiles, + errors.WithStack(runErr), + errors.WithStack(err) + } + + return testResults, testResultsFiles, errors.WithStack(runErr), nil +} + +func (s Service) runCommand( + ctx context.Context, + args []string, + stdout io.Writer, + env []string, +) (context.Context, error) { + cmd, err := s.TaskRunner.NewCommand(ctx, exec.CommandConfig{ + Name: args[0], + Args: args[1:], + Env: env, + Stdout: stdout, + Stderr: os.Stderr, + }) + if err != nil { + return ctx, errors.NewSystemError("unable to spawn sub-process: %s", err) + } + + s.Log.Debugf("Executing %q", strings.Join(args, " ")) + if err := cmd.Start(); err != nil { + return ctx, errors.NewSystemError("unable to execute sub-command: %s", err) + } + defer s.Log.Debugf("Finished executing %q", strings.Join(args, " ")) + + if err := cmd.Wait(); err != nil { + if code, e := s.TaskRunner.GetExitStatusFromError(err); e == nil { + return ctx, errors.NewExecutionError(code, "test suite exited with non-zero exit code") + } + + return ctx, errors.NewSystemError("Error during program execution: %s", err) + } + + return ctx, nil +} + +func (s Service) isIdentifiedIn(test v1.Test, identifiedTests []backend.Test) bool { + for _, identifiedTest := range identifiedTests { + compositeIdentifier, err := test.Identify(v1.TestIdentityRecipe{ + Components: identifiedTest.IdentityComponents, + Strict: identifiedTest.StrictIdentity, + }, + ) + if err != nil { + s.Log.Debugf("%v does not identify %v because %v", identifiedTest, test, err.Error()) + continue + } + + if compositeIdentifier != identifiedTest.CompositeIdentifier { + s.Log.Debugf( + "%v does not identify %v because they have different composite identifiers (%v != %v)", + identifiedTest, + test, + identifiedTest.CompositeIdentifier, + compositeIdentifier, + ) + continue + } + + s.Log.Debugf( + "%v identifies %v because they share the same composite identifier (%v)", + identifiedTest, + test, + compositeIdentifier, + ) + return true + } + + return false +} + +func (s Service) reportTestResults( + ctx context.Context, + cfg RunConfig, + testResults v1.TestResults, + newlyExecutedTestResults v1.TestResults, +) ([]backend.TestResultsUploadResult, error) { + if cfg.WriteRetryFailedTestsAction { + hasFailedTests := false + for _, test := range testResults.Tests { + if test.Attempt.Status.ImpliesFailure() { + hasFailedTests = true + break + } + } + + if hasFailedTests { + if cfg.RetryCommandTemplate == "" { + err := mint.WriteConfigureRetryCommandTip(s.FileSystem) + if err != nil { + return nil, errors.Wrap(err, "unable to write configure-captain-retry-command tip") + } + } else { + err := mint.WriteRetryFailedTestsAction(s.FileSystem, testResults, cfg.IntermediateArtifactsPath) + if err != nil { + return nil, errors.Wrap(err, "unable to write Mint retry failed tests action") + } + } + } + } + + reportingConfiguration := reporting.Configuration{ + SuiteID: cfg.SuiteID, + RetryCommandTemplate: cfg.RetryCommandTemplate, + } + + if remoteClient, ok := s.API.(remote.Client); ok { + reportingConfiguration.CloudEnabled = true + reportingConfiguration.CloudHost = remoteClient.Host + reportingConfiguration.CloudOrganizationSlug = cfg.CloudOrganizationSlug + reportingConfiguration.Provider = remoteClient.Provider + } + + for outputPath, writeReport := range cfg.Reporters { + file, err := s.FileSystem.Create(outputPath) + if err == nil { + err = writeReport(file, testResults, reportingConfiguration) + } + if err != nil { + s.Log.Warnf("Unable to write report to %s: %s", outputPath, err.Error()) + } + } + + if cfg.PrintSummary { + fmt.Fprintf(os.Stdout, "\n") + if err := reporting.WriteTextSummary(os.Stdout, newlyExecutedTestResults, reportingConfiguration); err != nil { + s.Log.Warnf("Unable to write text summary to stdout: %s", err.Error()) + } else { + // Append an empty line to make output more readable + fmt.Fprintf(os.Stdout, "\n") + } + } + + mintLinksPath := os.Getenv("MINT_LINKS") + if reportingConfiguration.CloudEnabled && + reportingConfiguration.Provider.ProviderName == "mint" && + mintLinksPath != "" { + backlinkPath := filepath.Join(mintLinksPath, "View RWX results") + backlinkURL := fmt.Sprintf( + "https://%v/captain/%v/test_suite_summaries/%v/%v/%v", + reportingConfiguration.CloudHost, + reportingConfiguration.CloudOrganizationSlug, + reportingConfiguration.SuiteID, + reportingConfiguration.Provider.BranchName, + reportingConfiguration.Provider.CommitSha, + ) + if err := os.WriteFile(backlinkPath, []byte(backlinkURL), 0o600); err != nil { + s.Log.Warnf("Could not populate backlink in Mint") + } + } + + if _, ok := s.API.(remote.Client); ok && !cfg.UploadResults { + return nil, nil + } + + result, err := s.API.UpdateTestResults(ctx, cfg.SuiteID, newlyExecutedTestResults) + if err != nil { + return nil, errors.Wrap(err, "unable to update test results") + } + + return result, nil +} + +func (s Service) printHeader() { + s.Log.Infoln(strings.Repeat("-", 80)) + s.Log.Infoln(fmt.Sprintf("%v RWX Test %v", strings.Repeat("-", 40-5-1), strings.Repeat("-", 40-4-1))) + s.Log.Infoln(strings.Repeat("-", 80)) +} + +func pluralize(count int, singular string, plural string) string { + if count == 1 { + return singular + } + + return plural +} + +func rwxOtelSpanAttributes( + testResults v1.TestResults, + suiteID string, +) map[string]any { + quarantinedFailures := 0 + actionableFailures := 0 + for _, test := range testResults.Tests { + if test.Attempt.Status.Kind == v1.TestStatusQuarantined { + quarantinedFailures++ + } else if test.Attempt.Status.ImpliesFailure() { + actionableFailures++ + } + } + + return map[string]any{ + "rwx.tests.suite-id": suiteID, + "rwx.tests.summary.status": string(testResults.Summary.Status), + "rwx.tests.summary.tests": testResults.Summary.Tests, + "rwx.tests.summary.flaky": testResults.Summary.Flaky, + "rwx.tests.summary.other-errors": testResults.Summary.OtherErrors, + "rwx.tests.summary.retries": testResults.Summary.Retries, + "rwx.tests.summary.canceled": testResults.Summary.Canceled, + "rwx.tests.summary.failed": testResults.Summary.Failed, + "rwx.tests.summary.pended": testResults.Summary.Pended, + "rwx.tests.summary.quarantined": testResults.Summary.Quarantined, + "rwx.tests.summary.skipped": testResults.Summary.Skipped, + "rwx.tests.summary.successful": testResults.Summary.Successful, + "rwx.tests.summary.timed-out": testResults.Summary.TimedOut, + "rwx.tests.summary.todo": testResults.Summary.Todo, + "rwx.tests.failures.quarantined": quarantinedFailures, + "rwx.tests.failures.actionable": actionableFailures, + } +} diff --git a/internal/captain/cli/run_command.go b/internal/captain/cli/run_command.go new file mode 100644 index 0000000..a90f202 --- /dev/null +++ b/internal/captain/cli/run_command.go @@ -0,0 +1,95 @@ +package cli + +import ( + "context" + "fmt" + + "github.com/mattn/go-shellwords" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/runpartition" + "github.com/rwx-cloud/cli/internal/captain/templating" +) + +// RunCommand represents the command that captain run ultimately execute. +// Typically this is executing the underlying test framework. +type RunCommand struct { + commandArgs []string + shortCircuit bool + shortCircuitInfo string +} + +func commandArgs(command string, args []string) ([]string, error) { + commandArgs := make([]string, 0) + + if command != "" { + parsedCommand, err := shellwords.Parse(command) + if err != nil { + return nil, errors.Wrapf(err, "Unable to parse %q into shell arguments", command) + } + commandArgs = append(commandArgs, parsedCommand...) + } + + commandArgs = append(commandArgs, args...) + + if len(commandArgs) == 0 { + return nil, errors.NewInputError("No command was provided") + } + + return commandArgs, nil +} + +func (s Service) makeRunCommand(ctx context.Context, cfg RunConfig) (RunCommand, error) { + if !cfg.IsRunningPartition() { + commandArgs, err := commandArgs(cfg.Command, cfg.Args) + if err != nil { + return RunCommand{}, err + } + return RunCommand{commandArgs: commandArgs, shortCircuit: false}, nil + } + + partitionResult, err := s.calculatePartition(ctx, cfg.PartitionConfig) + if err != nil { + return RunCommand{}, errors.WithStack(err) + } + partitionedTestFilePaths := partitionResult.partition.TestFilePaths + + // compile template + compiledPartitionTemplate, err := templating.CompileTemplate(cfg.PartitionCommandTemplate) + if err != nil { + return RunCommand{}, errors.WithStack(err) + } + + // validate template + substitution := runpartition.DelimiterSubstitution{Delimiter: cfg.PartitionConfig.Delimiter} + if err := substitution.ValidateTemplate(compiledPartitionTemplate); err != nil { + return RunCommand{}, errors.WithStack(err) + } + + // substitute template keywords with values + substitutionValueLookup, err := substitution.SubstitutionLookupFor(compiledPartitionTemplate, partitionedTestFilePaths) + if err != nil { + return RunCommand{}, errors.WithStack(err) + } + partitionCommand := compiledPartitionTemplate.Substitute(substitutionValueLookup) + + commandArgs, err := commandArgs(partitionCommand, cfg.Args) + if err != nil { + return RunCommand{}, err + } + + if len(partitionedTestFilePaths) == 0 { + infoMessage := fmt.Sprintf( + "Partition %v contained no test files. %d/%d partitions were utilized. "+ + "We recommend you set --partition-total no more than %d", + cfg.PartitionConfig.PartitionNodes, + partitionResult.utilizedPartitionCount, + cfg.PartitionConfig.PartitionNodes.Total, + partitionResult.utilizedPartitionCount, + ) + // short circuit to avoid running the entire test suite in a single partition (e.g empty partition) + return RunCommand{commandArgs: commandArgs, shortCircuit: true, shortCircuitInfo: infoMessage}, nil + } + + return RunCommand{commandArgs: commandArgs, shortCircuit: false}, nil +} diff --git a/internal/captain/cli/run_test.go b/internal/captain/cli/run_test.go new file mode 100644 index 0000000..a264541 --- /dev/null +++ b/internal/captain/cli/run_test.go @@ -0,0 +1,2668 @@ +package cli_test + +import ( + "context" + "fmt" + "io" + iofs "io/fs" + "net/http" + "os" + "strings" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" + "go.uber.org/zap/zaptest/observer" + + "github.com/rwx-cloud/cli/internal/captain/backend" + "github.com/rwx-cloud/cli/internal/captain/backend/remote" + "github.com/rwx-cloud/cli/internal/captain/cli" + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/exec" + "github.com/rwx-cloud/cli/internal/captain/fs" + "github.com/rwx-cloud/cli/internal/captain/mocks" + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Run", func() { + var ( + arg string + testResultsFilePath string + err error + ctx context.Context + mockCommand *mocks.Command + service cli.Service + core zapcore.Core + recordedLogs *observer.ObservedLogs + filesOpened []string + runConfig cli.RunConfig + + testResultsFileUploaded, commandStarted, commandFinished, fetchedRunConfiguration bool + ) + + BeforeEach(func() { + ctx = context.Background() + err = nil + testResultsFileUploaded = false + commandStarted = false + commandFinished = false + fetchedRunConfiguration = false + filesOpened = make([]string, 0) + + core, recordedLogs = observer.New(zapcore.InfoLevel) + log := zaptest.NewLogger(GinkgoT(), zaptest.WrapOptions( + zap.WrapCore(func(_ zapcore.Core) zapcore.Core { return core }), + )).Sugar() + service = cli.Service{ + API: new(mocks.API), + Log: log, + FileSystem: new(mocks.FileSystem), + TaskRunner: new(mocks.TaskRunner), + ParseConfig: parsing.Config{ + MutuallyExclusiveParsers: []parsing.Parser{new(mocks.Parser)}, + Logger: log, + }, + } + + // Set up default mock for GetQuarantinedTests that returns empty slice + mockGetQuarantinedTests := func( + _ context.Context, + _ string, + ) ([]backend.Test, error) { + return []backend.Test{}, nil + } + service.API.(*mocks.API).MockGetQuarantinedTests = mockGetQuarantinedTests + + mockCommand = new(mocks.Command) + mockCommand.MockStart = func() error { + commandStarted = true + return nil + } + + newCommand := func(_ context.Context, cfg exec.CommandConfig) (exec.Command, error) { + switch cfg.Name { + case "retry": + mockBundle := new(mocks.Command) + mockBundle.MockStart = func() error { + return nil + } + return mockBundle, nil + default: + Expect(cfg.Args).To(HaveLen(0)) + Expect(cfg.Name).To(Equal(arg)) + Expect(cfg.Env).To(HaveLen(0)) + return mockCommand, nil + } + } + service.TaskRunner.(*mocks.TaskRunner).MockNewCommand = newCommand + + service.ParseConfig.MutuallyExclusiveParsers[0].(*mocks.Parser).MockParse = func(_ io.Reader) ( + *v1.TestResults, + error, + ) { + return &v1.TestResults{}, nil + } + + // Caution: This needs to be an existing file. We don't actually read from it, however the glob expansion + // functionality is not mocked, i.e. it still uses the file-system. + testResultsFilePath = "run.go" + + mockGlob := func(_ string) ([]string, error) { + return []string{testResultsFilePath}, nil + } + mockOpen := func(name string) (fs.File, error) { + Expect(name).To(Equal(testResultsFilePath)) + filesOpened = append(filesOpened, name) + + file := new(mocks.File) + file.Reader = strings.NewReader("") + return file, nil + } + mockGetwd := func() (string, error) { + return "/go/github.com/rwx-research/captain-cli", nil + } + mockMkdirTemp := func(_, _ string) (string, error) { + return "/tmp/captain-test", nil + } + mockMkdirAll := func(_ string, _ os.FileMode) error { + return nil + } + mockCreate := func(_ string) (fs.File, error) { + return &mocks.File{ + Builder: new(strings.Builder), + Reader: strings.NewReader(""), + }, nil + } + mockRemove := func(string) error { + return nil + } + + service.FileSystem.(*mocks.FileSystem).MockOpen = mockOpen + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + service.FileSystem.(*mocks.FileSystem).MockGetwd = mockGetwd + service.FileSystem.(*mocks.FileSystem).MockMkdirTemp = mockMkdirTemp + service.FileSystem.(*mocks.FileSystem).MockMkdirAll = mockMkdirAll + service.FileSystem.(*mocks.FileSystem).MockCreate = mockCreate + service.FileSystem.(*mocks.FileSystem).MockRemove = mockRemove + + arg = fmt.Sprintf("fake-command-%d", GinkgoRandomSeed()) + runConfig = cli.RunConfig{ + Command: arg, + TestResultsFileGlob: testResultsFilePath, + SuiteID: "test", + } + }) + + JustBeforeEach(func() { + err = service.RunSuite(ctx, runConfig) + }) + + Context("with an invalid run config", func() { + BeforeEach(func() { + runConfig = cli.RunConfig{ + Args: []string{arg}, + TestResultsFileGlob: testResultsFilePath, + SuiteID: "test", + Retries: 1, + RetryCommandTemplate: "", + } + }) + + It("errs", func() { + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing retry command")) + }) + }) + + Context("under expected conditions", func() { + BeforeEach(func() { + mockUploadTestResults := func( + _ context.Context, + _ string, + _ v1.TestResults, + ) ([]backend.TestResultsUploadResult, error) { + testResultsFileUploaded = true + return []backend.TestResultsUploadResult{{OriginalPaths: []string{testResultsFilePath}, Uploaded: true}}, nil + } + service.API.(*mocks.API).MockUpdateTestResults = mockUploadTestResults + + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + fetchedRunConfiguration = true + return backend.RunConfiguration{}, nil + } + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + + mockCommand.MockWait = func() error { + commandFinished = true + return nil + } + }) + + It("doesn't return an error", func() { + Expect(err).ToNot(HaveOccurred()) + }) + + It("runs the supplied command", func() { + Expect(commandStarted).To(BeTrue()) + }) + + It("waits until the supplied command stopped running", func() { + Expect(commandFinished).To(BeTrue()) + }) + + It("fetches the run configuration with the quarantined tests", func() { + Expect(fetchedRunConfiguration).To(BeTrue()) + }) + + It("reads the test results file", func() { + Expect(filesOpened).To(ContainElement(testResultsFilePath)) + }) + + It("uploads the test results file", func() { + Expect(testResultsFileUploaded).To(BeTrue()) + }) + + It("logs the uploaded files", func() { + logMessages := make([]string, 0) + + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement(ContainSubstring("Found 1 test result file"))) + Expect(logMessages).To(ContainElement(ContainSubstring( + fmt.Sprintf("- Updated RWX with results from %v", testResultsFilePath), + ))) + }) + + Context("With uploading disabled and a remote client", func() { + var mockRoundTripper func(*http.Request) (*http.Response, error) + + BeforeEach(func() { + runConfig = cli.RunConfig{ + Command: arg, + TestResultsFileGlob: testResultsFilePath, + SuiteID: "test", + UploadResults: false, + } + mockRoundTripper = func(req *http.Request) (*http.Response, error) { + var resp http.Response + + Expect(req.Method).To(Equal(http.MethodGet)) + + if strings.HasSuffix(req.URL.Path, "quarantined_tests") { + resp.Body = io.NopCloser(strings.NewReader(`[]`)) + } else { + Expect(req.URL.Path).To(HaveSuffix("run_configuration")) + resp.Body = io.NopCloser(strings.NewReader(`{}`)) + } + + return &resp, nil + } + service.API = remote.Client{RoundTrip: mockRoundTripper} + }) + + It("runs the supplied command", func() { + Expect(commandStarted).To(BeTrue()) + }) + + It("waits until the supplied command stopped running", func() { + Expect(commandFinished).To(BeTrue()) + }) + + It("reads the test results file", func() { + Expect(filesOpened).To(ContainElement(testResultsFilePath)) + }) + + It("does not upload the test results file", func() { + Expect(testResultsFileUploaded).To(BeFalse()) + }) + + It("does not log the uploaded files", func() { + logMessages := make([]string, 0) + + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).NotTo(ContainElement(ContainSubstring("Found 1 test result file"))) + Expect(logMessages).NotTo(ContainElement(ContainSubstring( + fmt.Sprintf("- Updated RWX with results from %v", testResultsFilePath), + ))) + }) + }) + + Context("RWX OTEL span attributes", func() { + var ( + otelSpanPath string + createdFiles map[string]*strings.Builder + existingFile map[string]bool + ) + + BeforeEach(func() { + otelSpanPath = "/tmp/rwx-otel-span" + Expect(os.Setenv("RWX_OTEL_SPAN", otelSpanPath)).To(Succeed()) + DeferCleanup(func() { + Expect(os.Unsetenv("RWX_OTEL_SPAN")).To(Succeed()) + }) + + firstTestDescription := fmt.Sprintf("first-successful-description-%d", GinkgoRandomSeed()+1) + secondTestDescription := fmt.Sprintf("second-failed-description-%d", GinkgoRandomSeed()+2) + thirdTestDescription := fmt.Sprintf("third-timeout-description-%d", GinkgoRandomSeed()+3) + + service.ParseConfig.MutuallyExclusiveParsers[0].(*mocks.Parser).MockParse = func(_ io.Reader) ( + *v1.TestResults, + error, + ) { + return &v1.TestResults{ + Tests: []v1.Test{ + { + Name: firstTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Name: secondTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Name: thirdTestDescription, + Location: &v1.Location{File: "/other/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + }, + }, nil + } + + service.API.(*mocks.API).MockGetQuarantinedTests = func( + _ context.Context, + _ string, + ) ([]backend.Test, error) { + return []backend.Test{ + { + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", thirdTestDescription, "/other/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, nil + } + + createdFiles = make(map[string]*strings.Builder) + existingFile = make(map[string]bool) + + service.FileSystem.(*mocks.FileSystem).MockStat = func(name string) (os.FileInfo, error) { + if existingFile[name] { + return mocks.FileInfo{}, nil + } + + return nil, os.ErrNotExist + } + service.FileSystem.(*mocks.FileSystem).MockCreate = func(path string) (fs.File, error) { + builder := new(strings.Builder) + createdFiles[path] = builder + + return &mocks.File{ + Builder: builder, + Reader: strings.NewReader(""), + }, nil + } + }) + + It("writes final span attributes in RWX OTEL span path", func() { + Expect(err).NotTo(HaveOccurred()) + + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.suite-id.json", otelSpanPath)].String()).To(Equal(`"test"`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.summary.status.json", otelSpanPath)].String()).To(Equal(`"failed"`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.summary.tests.json", otelSpanPath)].String()).To(Equal(`3`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.summary.flaky.json", otelSpanPath)].String()).To(Equal(`0`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.summary.other-errors.json", otelSpanPath)].String()).To(Equal(`0`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.summary.retries.json", otelSpanPath)].String()).To(Equal(`0`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.summary.canceled.json", otelSpanPath)].String()).To(Equal(`0`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.summary.failed.json", otelSpanPath)].String()).To(Equal(`1`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.summary.pended.json", otelSpanPath)].String()).To(Equal(`0`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.summary.quarantined.json", otelSpanPath)].String()).To(Equal(`1`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.summary.skipped.json", otelSpanPath)].String()).To(Equal(`0`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.summary.successful.json", otelSpanPath)].String()).To(Equal(`1`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.summary.timed-out.json", otelSpanPath)].String()).To(Equal(`0`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.summary.todo.json", otelSpanPath)].String()).To(Equal(`0`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.failures.quarantined.json", otelSpanPath)].String()).To(Equal(`1`)) + Expect(createdFiles[fmt.Sprintf("%s/rwx.tests.failures.actionable.json", otelSpanPath)].String()).To(Equal(`1`)) + }) + + Context("when an attribute file already exists", func() { + BeforeEach(func() { + existingFile[fmt.Sprintf("%s/rwx.tests.suite-id.json", otelSpanPath)] = true + }) + + It("warns and skips writing all attributes", func() { + Expect(err).NotTo(HaveOccurred()) + Expect(createdFiles).NotTo(HaveKey(fmt.Sprintf("%s/rwx.tests.summary.tests.json", otelSpanPath))) + Expect(createdFiles).NotTo(HaveKey(fmt.Sprintf("%s/rwx.tests.summary.flaky.json", otelSpanPath))) + + logMessages := make([]string, 0) + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement( + ContainSubstring("Skipping RWX OTEL span attributes because they were already written"), + )) + }) + }) + + Context("when writing attributes fails", func() { + BeforeEach(func() { + service.FileSystem.(*mocks.FileSystem).MockCreate = func(path string) (fs.File, error) { + if strings.HasPrefix(path, otelSpanPath+"/") { + return nil, iofs.ErrPermission + } + + builder := new(strings.Builder) + createdFiles[path] = builder + + return &mocks.File{ + Builder: builder, + Reader: strings.NewReader(""), + }, nil + } + }) + + It("warns but does not fail the run", func() { + Expect(err).NotTo(HaveOccurred()) + + logMessages := make([]string, 0) + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement(ContainSubstring("Unable to write RWX OTEL span attributes"))) + }) + }) + }) + }) + + Context("with an erroring command", func() { + var ( + exitCode int + firstFailedTestDescription string + secondFailedTestDescription string + firstSuccessfulTestDescription string + uploadedTestResults *v1.TestResults + ) + + BeforeEach(func() { + uploadedTestResults = nil + exitCode = int(GinkgoRandomSeed() + 1) + firstFailedTestDescription = fmt.Sprintf("first-failed-description-%d", GinkgoRandomSeed()+2) + secondFailedTestDescription = fmt.Sprintf("second-failed-description-%d", GinkgoRandomSeed()+3) + firstSuccessfulTestDescription = fmt.Sprintf("first-successful-description-%d", GinkgoRandomSeed()+4) + + mockGetExitStatusFromError := func(error) (int, error) { + return exitCode, nil + } + service.TaskRunner.(*mocks.TaskRunner).MockGetExitStatusFromError = mockGetExitStatusFromError + + service.ParseConfig.MutuallyExclusiveParsers[0].(*mocks.Parser).MockParse = func(_ io.Reader) ( + *v1.TestResults, + error, + ) { + return &v1.TestResults{ + Tests: []v1.Test{ + { + Name: firstSuccessfulTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Name: firstFailedTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Name: secondFailedTestDescription, + Location: &v1.Location{File: "/other/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + }, + }, nil + } + + mockUploadTestResults := func( + _ context.Context, + _ string, + testResults v1.TestResults, + ) ([]backend.TestResultsUploadResult, error) { + testResultsFileUploaded = true + + uploadedTestResults = &testResults + + return []backend.TestResultsUploadResult{ + {OriginalPaths: []string{testResultsFilePath}, Uploaded: true}, + {OriginalPaths: []string{"./fake/path/1.json", "./fake/path/2.json"}, Uploaded: false}, + }, nil + } + service.API.(*mocks.API).MockUpdateTestResults = mockUploadTestResults + }) + + Context("no quarantined tests", func() { + It("returns the error code of the command", func() { + Expect(err).To(HaveOccurred()) + executionError, ok := errors.AsExecutionError(err) + Expect(ok).To(BeTrue(), "Error is an execution error") + Expect(executionError.Code).To(Equal(exitCode)) + }) + + It("logs details about the file uploads", func() { + logMessages := make([]string, 0) + + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement(ContainSubstring("Found 3 test result files:"))) + Expect(logMessages).To(ContainElement(fmt.Sprintf("- Updated RWX with results from %v", testResultsFilePath))) + Expect(logMessages).To(ContainElement("- Unable to update RWX with results from ./fake/path/1.json")) + Expect(logMessages).To(ContainElement("- Unable to update RWX with results from ./fake/path/2.json")) + }) + }) + + Context("other unknown tests quarantined", func() { + BeforeEach(func() { + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{ + QuarantinedTests: []backend.QuarantinedTest{ + { + Test: backend.Test{ + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", secondFailedTestDescription, "not/the/right/path.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, + }, + }, nil + } + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + }) + + It("returns the error code of the command", func() { + Expect(err).To(HaveOccurred()) + executionError, ok := errors.AsExecutionError(err) + Expect(ok).To(BeTrue(), "Error is an execution error") + Expect(executionError.Code).To(Equal(exitCode)) + }) + }) + + Context("other unknown tests quarantined", func() { + BeforeEach(func() { + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{ + QuarantinedTests: []backend.QuarantinedTest{ + { + Test: backend.Test{ + CompositeIdentifier: "some-description -captain- huh", + IdentityComponents: []string{"description", "huh"}, + StrictIdentity: true, + }, + }, + }, + }, nil + } + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + }) + + It("returns the error code of the command", func() { + Expect(err).To(HaveOccurred()) + executionError, ok := errors.AsExecutionError(err) + Expect(ok).To(BeTrue(), "Error is an execution error") + Expect(executionError.Code).To(Equal(exitCode)) + }) + + It("does not log any quarantined tests", func() { + logMessages := make([]string, 0) + + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).NotTo(ContainElement(ContainSubstring("under quarantine"))) + Expect(logMessages).NotTo(ContainElement(ContainSubstring("remaining actionable"))) + Expect(logMessages).NotTo(ContainElement(fmt.Sprintf("- %v", firstFailedTestDescription))) + Expect(logMessages).NotTo(ContainElement(fmt.Sprintf("- %v", secondFailedTestDescription))) + }) + }) + + Context("some tests quarantined", func() { + BeforeEach(func() { + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{ + QuarantinedTests: []backend.QuarantinedTest{ + { + Test: backend.Test{ + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", secondFailedTestDescription, "/other/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, + }, + }, nil + } + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + + // Also set up GetQuarantinedTests to return the same quarantined tests + mockGetQuarantinedTests := func( + _ context.Context, + _ string, + ) ([]backend.Test, error) { + return []backend.Test{ + { + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", secondFailedTestDescription, "/other/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, nil + } + service.API.(*mocks.API).MockGetQuarantinedTests = mockGetQuarantinedTests + }) + + It("returns the error code of the command", func() { + Expect(err).To(HaveOccurred()) + executionError, ok := errors.AsExecutionError(err) + Expect(ok).To(BeTrue(), "Error is an execution error") + Expect(executionError.Code).To(Equal(exitCode)) + }) + + It("logs the quarantined tests", func() { + logMessages := make([]string, 0) + + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement(ContainSubstring("1 of 2 failures under quarantine"))) + Expect(logMessages).To(ContainElement(ContainSubstring("1 remaining actionable failure"))) + Expect(logMessages).To(ContainElement(fmt.Sprintf("- %v", firstFailedTestDescription))) + Expect(logMessages).To(ContainElement(fmt.Sprintf("- %v", secondFailedTestDescription))) + }) + }) + + Context("all tests quarantined tests fail", func() { + BeforeEach(func() { + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{ + QuarantinedTests: []backend.QuarantinedTest{ + { + Test: backend.Test{ + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", firstFailedTestDescription, "/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, + { + Test: backend.Test{ + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", secondFailedTestDescription, "/other/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, + }, + }, nil + } + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + + // Also set up GetQuarantinedTests to return the same quarantined tests + mockGetQuarantinedTests := func( + _ context.Context, + _ string, + ) ([]backend.Test, error) { + return []backend.Test{ + { + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", firstFailedTestDescription, "/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + { + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", secondFailedTestDescription, "/other/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, nil + } + service.API.(*mocks.API).MockGetQuarantinedTests = mockGetQuarantinedTests + }) + + It("doesn't return an error", func() { + Expect(err).ToNot(HaveOccurred()) + }) + + It("logs the quarantined tests", func() { + logMessages := make([]string, 0) + + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement(ContainSubstring("2 of 2 failures under quarantine"))) + Expect(logMessages).NotTo(ContainElement(ContainSubstring("remaining actionable"))) + Expect(logMessages).To(ContainElement(fmt.Sprintf("- %v", firstFailedTestDescription))) + Expect(logMessages).To(ContainElement(fmt.Sprintf("- %v", secondFailedTestDescription))) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Quarantined).To(Equal(2)) + Expect(uploadedTestResults.Tests[0].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + + Expect(uploadedTestResults.Tests[1].Attempt.Status.Kind).To(Equal(v1.TestStatusQuarantined)) + Expect(uploadedTestResults.Tests[1].Attempt.Status.OriginalStatus.Kind).To(Equal(v1.TestStatusFailed)) + + Expect(uploadedTestResults.Tests[2].Attempt.Status.Kind).To(Equal(v1.TestStatusQuarantined)) + Expect(uploadedTestResults.Tests[2].Attempt.Status.OriginalStatus.Kind).To(Equal(v1.TestStatusTimedOut)) + }) + }) + + Context("some quarantined tests successful", func() { + BeforeEach(func() { + mockGetQuarantinedTests := func( + _ context.Context, + _ string, + ) ([]backend.Test, error) { + return []backend.Test{ + { + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", firstFailedTestDescription, "/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + { + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", secondFailedTestDescription, "/other/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + { + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", firstSuccessfulTestDescription, "/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, nil + } + service.API.(*mocks.API).MockGetQuarantinedTests = mockGetQuarantinedTests + }) + + It("doesn't return an error", func() { + Expect(err).ToNot(HaveOccurred()) + }) + + It("reports the original status of success", func() { + logMessages := make([]string, 0) + + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement(ContainSubstring("2 of 2 failures under quarantine"))) + Expect(logMessages).NotTo(ContainElement(ContainSubstring("remaining actionable"))) + Expect(logMessages).To(ContainElement(fmt.Sprintf("- %v", firstFailedTestDescription))) + Expect(logMessages).To(ContainElement(fmt.Sprintf("- %v", secondFailedTestDescription))) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Tests[0].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + + Expect(uploadedTestResults.Tests[1].Attempt.Status.Kind).To(Equal(v1.TestStatusQuarantined)) + Expect(uploadedTestResults.Tests[1].Attempt.Status.OriginalStatus.Kind).To(Equal(v1.TestStatusFailed)) + + Expect(uploadedTestResults.Tests[2].Attempt.Status.Kind).To(Equal(v1.TestStatusQuarantined)) + Expect(uploadedTestResults.Tests[2].Attempt.Status.OriginalStatus.Kind).To(Equal(v1.TestStatusTimedOut)) + }) + }) + + Context("test suite is quarantined", func() { + BeforeEach(func() { + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{ + IsSuiteQuarantined: true, + }, nil + } + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + }) + + It("doesn't return an error", func() { + Expect(err).ToNot(HaveOccurred()) + }) + + It("logs the failing tests", func() { + logMessages := make([]string, 0) + + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement(ContainSubstring("test suite exited with non-zero exit code"))) + Expect(logMessages).To(ContainElement( + ContainSubstring("Exiting with exit code 0 because the test suite is quarantined"), + )) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Quarantined).To(Equal(0)) + Expect(uploadedTestResults.Tests[0].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(uploadedTestResults.Tests[1].Attempt.Status.Kind).To(Equal(v1.TestStatusFailed)) + Expect(uploadedTestResults.Tests[2].Attempt.Status.Kind).To(Equal(v1.TestStatusTimedOut)) + }) + }) + + Context("test suite gets quarantined during execution", func() { + var callCount int + + BeforeEach(func() { + callCount = 0 + service.API.(*mocks.API).MockGetRunConfiguration = func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + callCount++ + if callCount == 1 { + // Initial call - suite not quarantined + return backend.RunConfiguration{ + IsSuiteQuarantined: false, + }, nil + } + // Subsequent calls - suite quarantined + return backend.RunConfiguration{ + IsSuiteQuarantined: true, + }, nil + } + }) + + It("returns nil when suite gets quarantined during execution", func() { + Expect(err).ToNot(HaveOccurred()) + }) + + It("logs the quarantine message when suite gets quarantined during execution", func() { + logMessages := make([]string, 0) + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement(ContainSubstring( + "Exiting with exit code 0 because the test suite is quarantined", + ))) + }) + }) + + Context("test suite quarantine check falls back when fresh fetch fails", func() { + var callCount int + + BeforeEach(func() { + callCount = 0 + service.API.(*mocks.API).MockGetRunConfiguration = func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + callCount++ + if callCount == 1 { + // First call - suite quarantined + return backend.RunConfiguration{ + IsSuiteQuarantined: true, + }, nil + } + // Subsequent calls - fail + return backend.RunConfiguration{}, errors.NewInternalError("API error") + } + }) + + It("returns nil when fresh fetch fails but original config has suite quarantined", func() { + Expect(err).ToNot(HaveOccurred()) + }) + + It("logs fallback warning when fresh fetch fails", func() { + logMessages := make([]string, 0) + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement(ContainSubstring( + "Failed to get the updated run configuration, will fall back to the original", + ))) + Expect(logMessages).To(ContainElement(ContainSubstring( + "Exiting with exit code 0 because the test suite is quarantined", + ))) + }) + }) + }) + + Context("with other errors", func() { + var ( + exitCode int + firstFailedTestDescription string + secondFailedTestDescription string + firstSuccessfulTestDescription string + ) + + BeforeEach(func() { + exitCode = int(GinkgoRandomSeed() + 1) + firstFailedTestDescription = fmt.Sprintf("first-failed-description-%d", GinkgoRandomSeed()+2) + secondFailedTestDescription = fmt.Sprintf("second-failed-description-%d", GinkgoRandomSeed()+3) + firstSuccessfulTestDescription = fmt.Sprintf("first-successful-description-%d", GinkgoRandomSeed()+4) + + mockGetExitStatusFromError := func(error) (int, error) { + return exitCode, nil + } + service.TaskRunner.(*mocks.TaskRunner).MockGetExitStatusFromError = mockGetExitStatusFromError + + service.ParseConfig.MutuallyExclusiveParsers[0].(*mocks.Parser).MockParse = func(_ io.Reader) ( + *v1.TestResults, + error, + ) { + return &v1.TestResults{ + OtherErrors: []v1.OtherError{ + { + Message: "Something went wrong", + }, + }, + Tests: []v1.Test{ + { + Name: firstSuccessfulTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Name: firstFailedTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Name: secondFailedTestDescription, + Location: &v1.Location{File: "/other/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + }, + }, nil + } + + mockUploadTestResults := func( + _ context.Context, + _ string, + _ v1.TestResults, + ) ([]backend.TestResultsUploadResult, error) { + testResultsFileUploaded = true + + return []backend.TestResultsUploadResult{ + {OriginalPaths: []string{testResultsFilePath}, Uploaded: true}, + {OriginalPaths: []string{"./fake/path/1.json", "./fake/path/2.json"}, Uploaded: false}, + }, nil + } + service.API.(*mocks.API).MockUpdateTestResults = mockUploadTestResults + + mockGetQuarantinedTests := func( + _ context.Context, + _ string, + ) ([]backend.Test, error) { + return []backend.Test{ + { + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", firstFailedTestDescription, "/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + { + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", secondFailedTestDescription, "/other/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, nil + } + service.API.(*mocks.API).MockGetQuarantinedTests = mockGetQuarantinedTests + }) + + It("returns the error code of the command", func() { + Expect(err).To(HaveOccurred()) + executionError, ok := errors.AsExecutionError(err) + Expect(ok).To(BeTrue(), "Error is an execution error") + Expect(executionError.Code).To(Equal(exitCode)) + }) + + It("logs the quarantined tests", func() { + logMessages := make([]string, 0) + + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement(ContainSubstring("2 of 2 failures under quarantine"))) + }) + + It("logs the other error count", func() { + logMessages := make([]string, 0) + + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement(ContainSubstring("1 other failure occurred"))) + }) + }) + + Describe("retries", func() { + var ( + parseCount int + exitCode int + uploadedTestResults *v1.TestResults + firstTestDescription string + secondTestDescription string + thirdTestDescription string + firstInitialStatus v1.TestStatus + secondInitialStatus v1.TestStatus + thirdInitialStatus v1.TestStatus + ) + + BeforeEach(func() { + parseCount = 0 + exitCode = int(GinkgoRandomSeed() + 1) + firstTestDescription = fmt.Sprintf("first-description-%d", GinkgoRandomSeed()+2) + secondTestDescription = fmt.Sprintf("second-description-%d", GinkgoRandomSeed()+3) + thirdTestDescription = fmt.Sprintf("third-description-%d", GinkgoRandomSeed()+4) + firstInitialStatus = v1.NewFailedTestStatus(nil, nil, nil) + secondInitialStatus = v1.NewFailedTestStatus(nil, nil, nil) + thirdInitialStatus = v1.NewFailedTestStatus(nil, nil, nil) + + mockRename := func(_, _ string) error { + return nil + } + service.FileSystem.(*mocks.FileSystem).MockRename = mockRename + + mockStat := func(string) (iofs.FileInfo, error) { + return nil, iofs.ErrNotExist + } + service.FileSystem.(*mocks.FileSystem).MockStat = mockStat + + mockGetExitStatusFromError := func(error) (int, error) { + return exitCode, nil + } + service.TaskRunner.(*mocks.TaskRunner).MockGetExitStatusFromError = mockGetExitStatusFromError + + mockUploadTestResults := func( + _ context.Context, + _ string, + testResults v1.TestResults, + ) ([]backend.TestResultsUploadResult, error) { + testResultsFileUploaded = true + + uploadedTestResults = &testResults + + return []backend.TestResultsUploadResult{ + {OriginalPaths: []string{testResultsFilePath}, Uploaded: true}, + {OriginalPaths: []string{"./fake/path/1.json", "./fake/path/2.json"}, Uploaded: false}, + }, nil + } + service.API.(*mocks.API).MockUpdateTestResults = mockUploadTestResults + + service.ParseConfig.MutuallyExclusiveParsers[0].(*mocks.Parser).MockParse = func(_ io.Reader) ( + *v1.TestResults, + error, + ) { + parseCount++ + firstStatus := firstInitialStatus + secondStatus := secondInitialStatus + thirdStatus := thirdInitialStatus + + switch parseCount { + case 2: + secondStatus = v1.NewSuccessfulTestStatus() + thirdStatus = v1.NewSuccessfulTestStatus() + case 3: + firstStatus = v1.NewSuccessfulTestStatus() + secondStatus = v1.NewSuccessfulTestStatus() + thirdStatus = v1.NewSuccessfulTestStatus() + } + + return &v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &firstTestDescription, + Name: firstTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: firstStatus, + }, + }, + { + ID: &secondTestDescription, + Name: secondTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: secondStatus, + }, + }, + { + ID: &thirdTestDescription, + Name: thirdTestDescription, + Location: &v1.Location{File: "/other/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: thirdStatus, + }, + }, + }, + }, nil + } + + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + fetchedRunConfiguration = true + return backend.RunConfiguration{}, nil + } + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + + runConfig = cli.RunConfig{ + Command: arg, + TestResultsFileGlob: testResultsFilePath, + SuiteID: "test", + Retries: 1, + RetryCommandTemplate: "retry {{ tests }}", + SubstitutionsByFramework: map[v1.Framework]targetedretries.Substitution{ + v1.RubyRSpecFramework: new(targetedretries.RubyRSpecSubstitution), + }, + } + }) + + Context("when there are no remaining failures after some retries", func() { + BeforeEach(func() { + runConfig.Retries = 5 + }) + + It("stops retrying", func() { + Expect(err).NotTo(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Successful).To(Equal(3)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(0)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(3)) + + Expect(uploadedTestResults.Tests[0].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(uploadedTestResults.Tests[0].PastAttempts).To(HaveLen(2)) + Expect(uploadedTestResults.Tests[0].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + Expect(uploadedTestResults.Tests[0].PastAttempts[1].Status.Kind).To(Equal(v1.TestStatusFailed)) + + Expect(uploadedTestResults.Tests[1].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(uploadedTestResults.Tests[1].PastAttempts).To(HaveLen(2)) + Expect(uploadedTestResults.Tests[1].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + Expect(uploadedTestResults.Tests[1].PastAttempts[1].Status.Kind).To(Equal(v1.TestStatusSuccessful)) + + Expect(uploadedTestResults.Tests[2].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(uploadedTestResults.Tests[2].PastAttempts).To(HaveLen(2)) + Expect(uploadedTestResults.Tests[2].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + Expect(uploadedTestResults.Tests[2].PastAttempts[1].Status.Kind).To(Equal(v1.TestStatusSuccessful)) + }) + }) + + Context("when there are pre- or post- retry commands", func() { + var ( + preRetryCommandFinished, postRetryCommandFinished bool + preRetryCommandStarted, postRetryCommandStarted bool + ) + + BeforeEach(func() { + runConfig.Retries = 1 + preRetryCommandStarted, preRetryCommandFinished = false, false + postRetryCommandStarted, postRetryCommandFinished = false, false + + mockPreRetryCommand := new(mocks.Command) + mockPreRetryCommand.MockStart = func() error { + Expect(commandFinished).To(BeTrue(), "the original command should run before the first retry") + + commandStarted = false + commandFinished = false + preRetryCommandStarted = true + + return nil + } + mockPreRetryCommand.MockWait = func() error { + Expect(preRetryCommandStarted).To(BeTrue()) + preRetryCommandFinished = true + return nil + } + + mockCommand.MockWait = func() error { + commandFinished = true + return nil + } + + mockPostRetryCommand := new(mocks.Command) + mockPostRetryCommand.MockStart = func() error { + Expect(commandFinished).To(BeTrue()) + Expect(preRetryCommandFinished).To(BeTrue()) + postRetryCommandStarted = true + return nil + } + mockPostRetryCommand.MockWait = func() error { + Expect(postRetryCommandStarted).To(BeTrue()) + postRetryCommandFinished = true + return nil + } + + newCommand := func(_ context.Context, cfg exec.CommandConfig) (exec.Command, error) { + switch cfg.Name { + case "pre": + Expect(cfg.Env).To(ContainElement(ContainSubstring("RWX_TEST_RETRY_ATTEMPT_NUMBER="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("RWX_TEST_RETRY_INVOCATION_NUMBER="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("RWX_TEST_RETRY_COMMAND_ID="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("CAPTAIN_RETRY_ATTEMPT_NUMBER="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("CAPTAIN_RETRY_INVOCATION_NUMBER="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("CAPTAIN_RETRY_COMMAND_ID="))) + return mockPreRetryCommand, nil + case "post": + Expect(cfg.Env).To(ContainElement(ContainSubstring("RWX_TEST_RETRY_ATTEMPT_NUMBER="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("RWX_TEST_RETRY_INVOCATION_NUMBER="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("RWX_TEST_RETRY_COMMAND_ID="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("CAPTAIN_RETRY_ATTEMPT_NUMBER="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("CAPTAIN_RETRY_INVOCATION_NUMBER="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("CAPTAIN_RETRY_COMMAND_ID="))) + return mockPostRetryCommand, nil + default: + if strings.Contains(cfg.Name, "retry") { + Expect(cfg.Env).To(ContainElement(ContainSubstring("RWX_TEST_RETRY_ATTEMPT_NUMBER="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("RWX_TEST_RETRY_INVOCATION_NUMBER="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("RWX_TEST_RETRY_COMMAND_ID="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("CAPTAIN_RETRY_ATTEMPT_NUMBER="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("CAPTAIN_RETRY_INVOCATION_NUMBER="))) + Expect(cfg.Env).To(ContainElement(ContainSubstring("CAPTAIN_RETRY_COMMAND_ID="))) + } else { + Expect(cfg.Env).NotTo(ContainElement(ContainSubstring("RWX_TEST_RETRY_ATTEMPT_NUMBER="))) + Expect(cfg.Env).NotTo(ContainElement(ContainSubstring("RWX_TEST_RETRY_INVOCATION_NUMBER="))) + Expect(cfg.Env).NotTo(ContainElement(ContainSubstring("RWX_TEST_RETRY_COMMAND_ID="))) + Expect(cfg.Env).NotTo(ContainElement(ContainSubstring("CAPTAIN_RETRY_ATTEMPT_NUMBER="))) + Expect(cfg.Env).NotTo(ContainElement(ContainSubstring("CAPTAIN_RETRY_INVOCATION_NUMBER="))) + Expect(cfg.Env).NotTo(ContainElement(ContainSubstring("CAPTAIN_RETRY_COMMAND_ID="))) + } + return mockCommand, nil + } + } + service.TaskRunner.(*mocks.TaskRunner).MockNewCommand = newCommand + + runConfig.PreRetryCommands = []string{"pre"} + runConfig.PostRetryCommands = []string{"post"} + }) + + It("executes the pre-retry command before the command and the post-retry command afterwards", func() { + Expect(err).ToNot(HaveOccurred()) + Expect(preRetryCommandStarted).To(BeTrue()) + Expect(preRetryCommandFinished).To(BeTrue()) + Expect(commandStarted).To(BeTrue()) + Expect(commandFinished).To(BeTrue()) + Expect(postRetryCommandStarted).To(BeTrue()) + Expect(postRetryCommandFinished).To(BeTrue()) + }) + }) + + Context("when a intermediate artifacts path is defined", func() { + var ( + intermediateTestResults []string + mkdirCalled bool + ) + + BeforeEach(func() { + runConfig.IntermediateArtifactsPath = fmt.Sprintf("intermediate-results-%d", GinkgoRandomSeed()) + runConfig.AdditionalArtifactPaths = []string{"coverage/**/*", "reports/**/*", "/tmp/external/**/*"} + runConfig.Retries = 2 + + mkdirCalled = false + + mockMkdirAll := func(dir string, _ os.FileMode) error { + Expect(dir).To(ContainSubstring(runConfig.IntermediateArtifactsPath)) + mkdirCalled = true + return nil + } + service.FileSystem.(*mocks.FileSystem).MockMkdirAll = mockMkdirAll + + mockRename := func(old, newName string) error { + Expect(old).To(Equal(testResultsFilePath)) + Expect(newName).To(ContainSubstring(runConfig.IntermediateArtifactsPath)) + intermediateTestResults = append(intermediateTestResults, newName) + return nil + } + service.FileSystem.(*mocks.FileSystem).MockRename = mockRename + + mockStat := func(name string) (iofs.FileInfo, error) { + switch name { + case runConfig.IntermediateArtifactsPath: + return mocks.FileInfo{Dir: true}, nil + default: + return nil, iofs.ErrNotExist + } + } + service.FileSystem.(*mocks.FileSystem).MockStat = mockStat + + intermediateTestResults = make([]string, 0) + }) + + It("moves artifacts to the configured directory", func() { + Expect(err).ToNot(HaveOccurred()) + Expect(mkdirCalled).To(BeTrue()) + Expect(intermediateTestResults).To(ContainElement( + fmt.Sprintf("%s/original-attempt/__rwx_working_directory/%s", + runConfig.IntermediateArtifactsPath, testResultsFilePath)), + ) + Expect(intermediateTestResults).To(ContainElement( + fmt.Sprintf("%s/retry-1/command-1/__rwx_working_directory/%s", + runConfig.IntermediateArtifactsPath, testResultsFilePath)), + ) + Expect(intermediateTestResults).To(ContainElement( + fmt.Sprintf("%s/retry-2/command-1/__rwx_working_directory/%s", + runConfig.IntermediateArtifactsPath, testResultsFilePath)), + ) + }) + }) + + Context("when there are failures left after all retries", func() { + BeforeEach(func() { + runConfig.Retries = 1 + }) + + It("stops retrying", func() { + Expect(err).To(HaveOccurred()) + executionError, ok := errors.AsExecutionError(err) + Expect(ok).To(BeTrue(), "Error is an execution error") + Expect(executionError.Code).To(Equal(exitCode)) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Successful).To(Equal(2)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(1)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(3)) + + Expect(uploadedTestResults.Tests[0].Attempt.Status.Kind).To(Equal(v1.TestStatusFailed)) + Expect(uploadedTestResults.Tests[0].PastAttempts).To(HaveLen(1)) + Expect(uploadedTestResults.Tests[0].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + + Expect(uploadedTestResults.Tests[1].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(uploadedTestResults.Tests[1].PastAttempts).To(HaveLen(1)) + Expect(uploadedTestResults.Tests[1].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + + Expect(uploadedTestResults.Tests[2].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(uploadedTestResults.Tests[2].PastAttempts).To(HaveLen(1)) + Expect(uploadedTestResults.Tests[2].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + }) + }) + + Context("when quarantining is set up", func() { + BeforeEach(func() { + runConfig.Retries = 1 + + mockGetQuarantinedTests := func( + _ context.Context, + _ string, + ) ([]backend.Test, error) { + return []backend.Test{ + { + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", firstTestDescription, "/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, nil + } + + service.API.(*mocks.API).MockGetQuarantinedTests = mockGetQuarantinedTests + }) + + It("quarantines any remaining failures that are marked as such", func() { + Expect(err).NotTo(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Successful).To(Equal(2)) + Expect(uploadedTestResults.Summary.Quarantined).To(Equal(1)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(0)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(3)) + + Expect(uploadedTestResults.Tests[0].Attempt.Status.Kind).To(Equal(v1.TestStatusQuarantined)) + Expect(uploadedTestResults.Tests[0].Attempt.Status.OriginalStatus.Kind).To(Equal(v1.TestStatusFailed)) + Expect(uploadedTestResults.Tests[0].PastAttempts).To(HaveLen(1)) + Expect(uploadedTestResults.Tests[0].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + + Expect(uploadedTestResults.Tests[1].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(uploadedTestResults.Tests[1].PastAttempts).To(HaveLen(1)) + Expect(uploadedTestResults.Tests[1].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + + Expect(uploadedTestResults.Tests[2].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(uploadedTestResults.Tests[2].PastAttempts).To(HaveLen(1)) + Expect(uploadedTestResults.Tests[2].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + }) + }) + + Context("when quarantined tests should not be retried", func() { + BeforeEach(func() { + runConfig.Retries = 2 + runConfig.QuarantinedTestRetries = 0 + + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{ + QuarantinedTests: []backend.QuarantinedTest{ + { + Test: backend.Test{ + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", firstTestDescription, "/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, + }, + }, nil + } + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + + mockGetQuarantinedTests := func( + _ context.Context, + _ string, + ) ([]backend.Test, error) { + return []backend.Test{ + { + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", firstTestDescription, "/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, nil + } + service.API.(*mocks.API).MockGetQuarantinedTests = mockGetQuarantinedTests + + service.ParseConfig.MutuallyExclusiveParsers[0].(*mocks.Parser).MockParse = func(_ io.Reader) ( + *v1.TestResults, + error, + ) { + parseCount++ + secondStatus := secondInitialStatus + thirdStatus := thirdInitialStatus + + if parseCount > 1 { + if parseCount == 2 { + secondStatus = v1.NewFailedTestStatus(nil, nil, nil) + thirdStatus = v1.NewFailedTestStatus(nil, nil, nil) + } else { + secondStatus = v1.NewSuccessfulTestStatus() + thirdStatus = v1.NewSuccessfulTestStatus() + } + + return &v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &secondTestDescription, + Name: secondTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: secondStatus, + }, + }, + { + ID: &thirdTestDescription, + Name: thirdTestDescription, + Location: &v1.Location{File: "/other/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: thirdStatus, + }, + }, + }, + }, nil + } + + firstStatus := firstInitialStatus + return &v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &firstTestDescription, + Name: firstTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: firstStatus, + }, + }, + { + ID: &secondTestDescription, + Name: secondTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: secondStatus, + }, + }, + { + ID: &thirdTestDescription, + Name: thirdTestDescription, + Location: &v1.Location{File: "/other/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: thirdStatus, + }, + }, + }, + }, nil + } + }) + + It("does not retry quarantined tests", func() { + Expect(err).NotTo(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Successful).To(Equal(2)) + Expect(uploadedTestResults.Summary.Quarantined).To(Equal(1)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(0)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(2)) + + Expect(uploadedTestResults.Tests[0].Attempt.Status.Kind).To(Equal(v1.TestStatusQuarantined)) + Expect(uploadedTestResults.Tests[0].Attempt.Status.OriginalStatus.Kind).To(Equal(v1.TestStatusFailed)) + Expect(uploadedTestResults.Tests[0].PastAttempts).To(HaveLen(0), "Quarantined test should not be retried") + + Expect(uploadedTestResults.Tests[1].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(uploadedTestResults.Tests[1].PastAttempts).To(HaveLen(2), "Non-quarantined test should be retried twice") + Expect(uploadedTestResults.Tests[1].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + Expect(uploadedTestResults.Tests[1].PastAttempts[1].Status.Kind).To(Equal(v1.TestStatusFailed)) + + Expect(uploadedTestResults.Tests[2].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(uploadedTestResults.Tests[2].PastAttempts).To(HaveLen(2), "Non-quarantined test should be retried twice") + Expect(uploadedTestResults.Tests[2].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + Expect(uploadedTestResults.Tests[2].PastAttempts[1].Status.Kind).To(Equal(v1.TestStatusFailed)) + }) + }) + + Context("when quarantined tests should be retried (default behavior)", func() { + BeforeEach(func() { + runConfig.Retries = 2 + runConfig.QuarantinedTestRetries = -1 + + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{ + QuarantinedTests: []backend.QuarantinedTest{ + { + Test: backend.Test{ + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", firstTestDescription, "/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, + }, + }, nil + } + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + + mockGetQuarantinedTests := func( + _ context.Context, + _ string, + ) ([]backend.Test, error) { + return []backend.Test{ + { + CompositeIdentifier: fmt.Sprintf("%v -captain- %v", firstTestDescription, "/path/to/file.test"), + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, nil + } + service.API.(*mocks.API).MockGetQuarantinedTests = mockGetQuarantinedTests + + service.ParseConfig.MutuallyExclusiveParsers[0].(*mocks.Parser).MockParse = func(_ io.Reader) ( + *v1.TestResults, + error, + ) { + parseCount++ + firstStatus := firstInitialStatus + secondStatus := secondInitialStatus + thirdStatus := thirdInitialStatus + + if parseCount > 1 { + if parseCount == 2 { + firstStatus = v1.NewFailedTestStatus(nil, nil, nil) + secondStatus = v1.NewFailedTestStatus(nil, nil, nil) + thirdStatus = v1.NewFailedTestStatus(nil, nil, nil) + } else { + firstStatus = v1.NewSuccessfulTestStatus() + secondStatus = v1.NewSuccessfulTestStatus() + thirdStatus = v1.NewSuccessfulTestStatus() + } + } + + return &v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &firstTestDescription, + Name: firstTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: firstStatus, + }, + }, + { + ID: &secondTestDescription, + Name: secondTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: secondStatus, + }, + }, + { + ID: &thirdTestDescription, + Name: thirdTestDescription, + Location: &v1.Location{File: "/other/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: thirdStatus, + }, + }, + }, + }, nil + } + }) + + It("retries quarantined tests when flag is false", func() { + Expect(err).NotTo(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Successful).To(Equal(3)) + Expect(uploadedTestResults.Summary.Quarantined).To(Equal(0)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(0)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(3)) + + Expect(uploadedTestResults.Tests[0].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(uploadedTestResults.Tests[0].PastAttempts).To(HaveLen(2), + "Quarantined test should be retried when flag is false") + Expect(uploadedTestResults.Tests[0].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + Expect(uploadedTestResults.Tests[0].PastAttempts[1].Status.Kind).To(Equal(v1.TestStatusFailed)) + + Expect(uploadedTestResults.Tests[1].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(uploadedTestResults.Tests[1].PastAttempts).To(HaveLen(2), "Non-quarantined test should be retried") + Expect(uploadedTestResults.Tests[1].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + Expect(uploadedTestResults.Tests[1].PastAttempts[1].Status.Kind).To(Equal(v1.TestStatusFailed)) + + Expect(uploadedTestResults.Tests[2].Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(uploadedTestResults.Tests[2].PastAttempts).To(HaveLen(2), "Non-quarantined test should be retried") + Expect(uploadedTestResults.Tests[2].PastAttempts[0].Status.Kind).To(Equal(v1.TestStatusFailed)) + Expect(uploadedTestResults.Tests[2].PastAttempts[1].Status.Kind).To(Equal(v1.TestStatusFailed)) + }) + }) + + Context("when retrying only flaky tests and there are no flaky tests", func() { + BeforeEach(func() { + runConfig.Retries = -1 + runConfig.FlakyRetries = 1 + + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{FlakyTests: []backend.Test{}}, nil + } + + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + }) + + It("does not retry", func() { + Expect(err).To(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(3)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(0)) + }) + }) + + Context("when retrying only flaky tests and only a subset of the failing tests are flaky", func() { + BeforeEach(func() { + runConfig.Retries = -1 + runConfig.FlakyRetries = 1 + + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{ + FlakyTests: []backend.Test{ + { + CompositeIdentifier: firstTestDescription, + IdentityComponents: []string{"description"}, + StrictIdentity: true, + }, + }, + }, nil + } + + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + + newCommand := func(_ context.Context, cfg exec.CommandConfig) (exec.Command, error) { + if cfg.Name == "retry" { + Expect(cfg.Args).To(ContainElement(ContainSubstring(firstTestDescription))) + Expect(cfg.Args).NotTo(ContainElement(ContainSubstring(secondTestDescription))) + Expect(cfg.Args).NotTo(ContainElement(ContainSubstring(thirdTestDescription))) + } + + return mockCommand, nil + } + service.TaskRunner.(*mocks.TaskRunner).MockNewCommand = newCommand + }) + + It("only retries the flaky ones", func() { + // see assertions in newCommand + Expect(err).To(HaveOccurred()) + }) + }) + + Context("when retrying flaky more than the non-flaky tests", func() { + var retryCount int + + BeforeEach(func() { + runConfig.Retries = 1 + runConfig.FlakyRetries = 2 + retryCount = 0 + + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{ + FlakyTests: []backend.Test{ + { + CompositeIdentifier: firstTestDescription, + IdentityComponents: []string{"description"}, + StrictIdentity: true, + }, + }, + }, nil + } + + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + + newCommand := func(_ context.Context, cfg exec.CommandConfig) (exec.Command, error) { + if cfg.Name == "retry" { + retryCount++ + + if retryCount == 1 { + Expect(cfg.Args).To(ContainElement(ContainSubstring(firstTestDescription))) + Expect(cfg.Args).To(ContainElement(ContainSubstring(secondTestDescription))) + Expect(cfg.Args).To(ContainElement(ContainSubstring(thirdTestDescription))) + } else { + Expect(cfg.Args).To(ContainElement(ContainSubstring(firstTestDescription))) + Expect(cfg.Args).NotTo(ContainElement(ContainSubstring(secondTestDescription))) + Expect(cfg.Args).NotTo(ContainElement(ContainSubstring(thirdTestDescription))) + } + } + + return mockCommand, nil + } + service.TaskRunner.(*mocks.TaskRunner).MockNewCommand = newCommand + + service.ParseConfig.MutuallyExclusiveParsers[0].(*mocks.Parser).MockParse = func(_ io.Reader) ( + *v1.TestResults, + error, + ) { + parseCount++ + + firstStatus := firstInitialStatus + secondStatus := secondInitialStatus + thirdStatus := thirdInitialStatus + + return &v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &firstTestDescription, + Name: firstTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: firstStatus, + }, + }, + { + ID: &secondTestDescription, + Name: secondTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: secondStatus, + }, + }, + { + ID: &thirdTestDescription, + Name: thirdTestDescription, + Location: &v1.Location{File: "/other/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: thirdStatus, + }, + }, + }, + }, nil + } + }) + + It("stops retrying the non-flaky ones after it exhausts the attempts", func() { + // see assertions in newCommand + Expect(err).To(HaveOccurred()) + }) + }) + + Context("when retrying non-flaky more than the flaky tests", func() { + var retryCount int + + BeforeEach(func() { + runConfig.Retries = 2 + runConfig.FlakyRetries = 1 + retryCount = 0 + + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{ + FlakyTests: []backend.Test{ + { + CompositeIdentifier: firstTestDescription, + IdentityComponents: []string{"description"}, + StrictIdentity: true, + }, + }, + }, nil + } + + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + + newCommand := func(_ context.Context, cfg exec.CommandConfig) (exec.Command, error) { + if cfg.Name == "retry" { + retryCount++ + + if retryCount == 1 { + Expect(cfg.Args).To(ContainElement(ContainSubstring(firstTestDescription))) + Expect(cfg.Args).To(ContainElement(ContainSubstring(secondTestDescription))) + Expect(cfg.Args).To(ContainElement(ContainSubstring(thirdTestDescription))) + } else { + Expect(cfg.Args).NotTo(ContainElement(ContainSubstring(firstTestDescription))) + Expect(cfg.Args).To(ContainElement(ContainSubstring(secondTestDescription))) + Expect(cfg.Args).To(ContainElement(ContainSubstring(thirdTestDescription))) + } + } + + return mockCommand, nil + } + service.TaskRunner.(*mocks.TaskRunner).MockNewCommand = newCommand + + service.ParseConfig.MutuallyExclusiveParsers[0].(*mocks.Parser).MockParse = func(_ io.Reader) ( + *v1.TestResults, + error, + ) { + parseCount++ + + firstStatus := firstInitialStatus + secondStatus := secondInitialStatus + thirdStatus := thirdInitialStatus + + return &v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &firstTestDescription, + Name: firstTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: firstStatus, + }, + }, + { + ID: &secondTestDescription, + Name: secondTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: secondStatus, + }, + }, + { + ID: &thirdTestDescription, + Name: thirdTestDescription, + Location: &v1.Location{File: "/other/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: thirdStatus, + }, + }, + }, + }, nil + } + }) + + It("stops retrying the flaky ones after it exhausts the attempts", func() { + // see assertions in newCommand + Expect(err).To(HaveOccurred()) + }) + }) + + Context("when retrying everything and there are flaky tests", func() { + BeforeEach(func() { + runConfig.Retries = 2 + runConfig.FlakyRetries = -1 + + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{ + FlakyTests: []backend.Test{ + { + CompositeIdentifier: firstTestDescription, + IdentityComponents: []string{"description"}, + StrictIdentity: true, + }, + }, + }, nil + } + + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + + newCommand := func(_ context.Context, cfg exec.CommandConfig) (exec.Command, error) { + if cfg.Name == "retry" { + Expect(cfg.Args).To(ContainElement(ContainSubstring(firstTestDescription))) + Expect(cfg.Args).To(ContainElement(ContainSubstring(secondTestDescription))) + Expect(cfg.Args).To(ContainElement(ContainSubstring(thirdTestDescription))) + } + + return mockCommand, nil + } + service.TaskRunner.(*mocks.TaskRunner).MockNewCommand = newCommand + + service.ParseConfig.MutuallyExclusiveParsers[0].(*mocks.Parser).MockParse = func(_ io.Reader) ( + *v1.TestResults, + error, + ) { + parseCount++ + + firstStatus := firstInitialStatus + secondStatus := secondInitialStatus + thirdStatus := thirdInitialStatus + + return &v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &firstTestDescription, + Name: firstTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: firstStatus, + }, + }, + { + ID: &secondTestDescription, + Name: secondTestDescription, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: secondStatus, + }, + }, + { + ID: &thirdTestDescription, + Name: thirdTestDescription, + Location: &v1.Location{File: "/other/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: thirdStatus, + }, + }, + }, + }, nil + } + }) + + It("retries all tests until the attempts are exhausted, flaky or not", func() { + // see assertions in newCommand + Expect(err).To(HaveOccurred()) + }) + }) + + Context("when failing fast and there are non-flaky failures that can't pass", func() { + BeforeEach(func() { + runConfig.Retries = -1 + runConfig.FlakyRetries = 1 + runConfig.FailRetriesFast = true + + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{ + FlakyTests: []backend.Test{ + { + CompositeIdentifier: firstTestDescription, + IdentityComponents: []string{"description"}, + StrictIdentity: true, + }, + }, + }, nil + } + + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + }) + + It("fails quickly", func() { + Expect(err).To(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(3)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(0)) + }) + }) + + Context("when failing fast and there are non-flaky failures that pass fast enough", func() { + BeforeEach(func() { + runConfig.Retries = 1 + runConfig.FlakyRetries = 2 + runConfig.FailRetriesFast = true + + mockGetRunConfiguration := func( + _ context.Context, + _ string, + ) (backend.RunConfiguration, error) { + return backend.RunConfiguration{ + FlakyTests: []backend.Test{ + { + CompositeIdentifier: firstTestDescription, + IdentityComponents: []string{"description"}, + StrictIdentity: true, + }, + }, + }, nil + } + + service.API.(*mocks.API).MockGetRunConfiguration = mockGetRunConfiguration + }) + + It("finishes retrying", func() { + Expect(err).NotTo(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Successful).To(Equal(3)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(0)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(3)) + }) + }) + + Context("when there are too many failures by percent", func() { + BeforeEach(func() { + runConfig.MaxTestsToRetry = "50%" + runConfig.Retries = 2 + }) + + It("does not retry", func() { + Expect(err).To(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(3)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(0)) + }) + }) + + Context("when there are not too many failures by percent", func() { + BeforeEach(func() { + runConfig.MaxTestsToRetry = "200%" + runConfig.Retries = 2 + }) + + It("retries", func() { + Expect(err).NotTo(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Successful).To(Equal(3)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(0)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(3)) + }) + }) + + Context("when there are too many failures by count", func() { + BeforeEach(func() { + runConfig.MaxTestsToRetry = "1" + runConfig.Retries = 2 + }) + + It("does not retry", func() { + Expect(err).To(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(3)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(0)) + }) + }) + + Context("when there are not too many failures by count", func() { + BeforeEach(func() { + runConfig.MaxTestsToRetry = "5" + runConfig.Retries = 2 + }) + + It("retries", func() { + Expect(err).NotTo(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Successful).To(Equal(3)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(0)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(3)) + }) + }) + + Context("when using a custom command w/ JSON even though the framework is supported", func() { + var removedFiles []string + + BeforeEach(func() { + service.FileSystem.(*mocks.FileSystem).MockRemove = func(name string) error { + removedFiles = append(removedFiles, name) + return nil + } + + service.FileSystem.(*mocks.FileSystem).MockCreateTemp = func(_ string, _ string) (fs.File, error) { + mockFile := new(mocks.File) + mockFile.Builder = new(strings.Builder) + mockFile.MockName = func() string { + return "json-temp-file" + } + return mockFile, nil + } + runConfig.Retries = 2 + runConfig.RetryCommandTemplate = "retry {{ jsonFilePath }}" + + newCommand := func(_ context.Context, cfg exec.CommandConfig) (exec.Command, error) { + if cfg.Name == "retry" { + Expect(cfg.Args).To(ContainElement(ContainSubstring("json-temp-file"))) + } + + return mockCommand, nil + } + service.TaskRunner.(*mocks.TaskRunner).MockNewCommand = newCommand + }) + + It("passes the JSON file to the retry command and cleans up", func() { + // see assertions in newCommand + Expect(err).NotTo(HaveOccurred()) + Expect(removedFiles).To(ContainElement("json-temp-file")) + }) + }) + + Context("when there are no failures", func() { + BeforeEach(func() { + firstInitialStatus = v1.NewSuccessfulTestStatus() + secondInitialStatus = v1.NewSuccessfulTestStatus() + thirdInitialStatus = v1.NewSuccessfulTestStatus() + mockCommand.MockWait = func() error { + commandFinished = true + return nil + } + }) + + It("does not retry", func() { + Expect(err).NotTo(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Successful).To(Equal(3)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(0)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(0)) + }) + }) + + Context("when the template cannot compile", func() { + BeforeEach(func() { + runConfig.RetryCommandTemplate = "bundle exec rspec {{ tests }} {{ tests }}" + }) + + It("does not retry", func() { + Expect(err).To(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Successful).To(Equal(0)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(3)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(0)) + }) + }) + + Context("when the framework does not have substitutions yet", func() { + BeforeEach(func() { + runConfig.SubstitutionsByFramework = map[v1.Framework]targetedretries.Substitution{} + }) + + It("does not retry", func() { + Expect(err).To(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Successful).To(Equal(0)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(3)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(0)) + }) + }) + + Context("when the template is not valid for the framework", func() { + BeforeEach(func() { + runConfig.RetryCommandTemplate = "bundle exec rspec {{ wat }}" + }) + + It("does not retry", func() { + Expect(err).To(HaveOccurred()) + + Expect(uploadedTestResults).ToNot(BeNil()) + Expect(uploadedTestResults.Summary.Tests).To(Equal(3)) + Expect(uploadedTestResults.Summary.Successful).To(Equal(0)) + Expect(uploadedTestResults.Summary.Failed).To(Equal(3)) + Expect(uploadedTestResults.Summary.Retries).To(Equal(0)) + }) + }) + + Context("when there are no results", func() { + BeforeEach(func() { + mockGlob := func(_ string) ([]string, error) { + return []string{}, nil + } + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + }) + + It("does not retry", func() { + Expect(err).To(HaveOccurred()) + executionError, ok := errors.AsExecutionError(err) + Expect(ok).To(BeTrue(), "Error is an execution error") + Expect(executionError.Code).To(Equal(exitCode)) + }) + }) + + Context("when additional artifacts are configured", func() { + var ( + intermediateTestResults []string + intermediateAdditionalFiles []string + globManyCalled bool + additionalArtifactFiles []string + ) + + BeforeEach(func() { + runConfig.IntermediateArtifactsPath = fmt.Sprintf("intermediate-results-%d", GinkgoRandomSeed()) + runConfig.AdditionalArtifactPaths = []string{"coverage/**/*", "reports/**/*", "/tmp/external/**/*"} + runConfig.Retries = 2 + + intermediateTestResults = make([]string, 0) + intermediateAdditionalFiles = make([]string, 0) + globManyCalled = false + + additionalArtifactFiles = []string{ + "coverage/lcov.info", + "coverage/coverage.json", + "reports/junit.xml", + "/tmp/external/performance.json", + } + + mockMkdirAll := func(_ string, _ os.FileMode) error { + return nil + } + service.FileSystem.(*mocks.FileSystem).MockMkdirAll = mockMkdirAll + + mockRename := func(old, newName string) error { + if old == testResultsFilePath { + // This is a test result file + if strings.Contains(newName, runConfig.IntermediateArtifactsPath) { + intermediateTestResults = append(intermediateTestResults, newName) + } + } else { + // This is an additional artifact file + if strings.Contains(newName, runConfig.IntermediateArtifactsPath) { + intermediateAdditionalFiles = append(intermediateAdditionalFiles, newName) + } + } + return nil + } + service.FileSystem.(*mocks.FileSystem).MockRename = mockRename + + mockGlobMany := func(patterns []string) ([]string, error) { + globManyCalled = true + if len(patterns) > 0 && patterns[0] == runConfig.AdditionalArtifactPaths[0] { + return additionalArtifactFiles, nil + } + return []string{}, nil + } + service.FileSystem.(*mocks.FileSystem).MockGlobMany = mockGlobMany + + mockStat := func(name string) (iofs.FileInfo, error) { + if name == runConfig.IntermediateArtifactsPath { + return mocks.FileInfo{Dir: true}, nil + } + return nil, iofs.ErrNotExist + } + service.FileSystem.(*mocks.FileSystem).MockStat = mockStat + + mockGetwd := func() (string, error) { + return "/test/working", nil + } + service.FileSystem.(*mocks.FileSystem).MockGetwd = mockGetwd + }) + + It("moves both test results and additional artifacts to intermediate directory", func() { + Expect(err).ToNot(HaveOccurred()) + Expect(globManyCalled).To(BeTrue()) + + Expect(intermediateTestResults).To(ContainElement( + fmt.Sprintf("%s/original-attempt/__rwx_working_directory/%s", + runConfig.IntermediateArtifactsPath, testResultsFilePath)), + ) + Expect(intermediateTestResults).To(ContainElement( + fmt.Sprintf("%s/retry-1/command-1/__rwx_working_directory/%s", + runConfig.IntermediateArtifactsPath, testResultsFilePath)), + ) + Expect(intermediateTestResults).To(ContainElement( + fmt.Sprintf("%s/retry-2/command-1/__rwx_working_directory/%s", + runConfig.IntermediateArtifactsPath, testResultsFilePath)), + ) + + Expect(intermediateAdditionalFiles).To(ContainElement( + fmt.Sprintf("%s/original-attempt/__rwx_working_directory/coverage/lcov.info", + runConfig.IntermediateArtifactsPath)), + ) + Expect(intermediateAdditionalFiles).To(ContainElement( + fmt.Sprintf("%s/original-attempt/__rwx_working_directory/coverage/coverage.json", + runConfig.IntermediateArtifactsPath)), + ) + Expect(intermediateAdditionalFiles).To(ContainElement( + fmt.Sprintf("%s/original-attempt/__rwx_working_directory/reports/junit.xml", + runConfig.IntermediateArtifactsPath)), + ) + + Expect(intermediateAdditionalFiles).To(ContainElement( + fmt.Sprintf("%s/original-attempt/tmp/external/performance.json", + runConfig.IntermediateArtifactsPath)), + ) + + Expect(intermediateAdditionalFiles).To(ContainElement( + fmt.Sprintf("%s/retry-1/command-1/__rwx_working_directory/coverage/lcov.info", + runConfig.IntermediateArtifactsPath)), + ) + Expect(intermediateAdditionalFiles).To(ContainElement( + fmt.Sprintf("%s/retry-2/command-1/__rwx_working_directory/coverage/lcov.info", + runConfig.IntermediateArtifactsPath)), + ) + + Expect(intermediateAdditionalFiles).To(ContainElement( + fmt.Sprintf("%s/retry-1/command-1/tmp/external/performance.json", + runConfig.IntermediateArtifactsPath)), + ) + }) + + Context("when no additional artifacts are found", func() { + BeforeEach(func() { + additionalArtifactFiles = []string{} + }) + + It("still moves test results but no additional artifacts", func() { + Expect(err).ToNot(HaveOccurred()) + + Expect(intermediateTestResults).To(ContainElement( + fmt.Sprintf("%s/original-attempt/__rwx_working_directory/%s", + runConfig.IntermediateArtifactsPath, testResultsFilePath)), + ) + + Expect(intermediateAdditionalFiles).To(BeEmpty()) + }) + }) + + Context("validation", func() { + It("requires intermediate artifacts path when additional artifact paths are specified", func() { + invalidConfig := cli.RunConfig{ + Command: arg, + TestResultsFileGlob: testResultsFilePath, + SuiteID: "test", + AdditionalArtifactPaths: []string{"coverage/**/*"}, + } + + logger := zaptest.NewLogger(GinkgoT()).Sugar() + err := invalidConfig.Validate(logger) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing intermediate artifacts path")) + }) + }) + }) + }) + + Context("when there are multiple frameworks parsed", func() { + var parseCount int + + BeforeEach(func() { + parseCount = 0 + + mockGetExitStatusFromError := func(error) (int, error) { + return 0, nil + } + service.TaskRunner.(*mocks.TaskRunner).MockGetExitStatusFromError = mockGetExitStatusFromError + + mockGlob := func(_ string) ([]string, error) { + return []string{testResultsFilePath, testResultsFilePath}, nil + } + service.FileSystem.(*mocks.FileSystem).MockGlob = mockGlob + + service.ParseConfig.MutuallyExclusiveParsers[0].(*mocks.Parser).MockParse = func(_ io.Reader) ( + *v1.TestResults, + error, + ) { + parseCount++ + + framework := v1.RubyRSpecFramework + if parseCount == 2 { + framework = v1.JavaScriptJestFramework + } + + return &v1.TestResults{ + Framework: framework, + Tests: []v1.Test{}, + }, nil + } + }) + + It("exits non-zero and logs an error", func() { + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring( + "Multiple frameworks detected. rwx test only works with one framework at a time", + )) + }) + }) + + Context("CreateRetryFilter", func() { + var ( + cfg cli.RunConfig + apiConfig backend.RunConfiguration + remainingFlakyFailures []v1.Test + retries, flakyRetries, nonFlakyRetries int + ) + + BeforeEach(func() { + cfg = cli.RunConfig{ + QuarantinedTestRetries: -1, + } + apiConfig = backend.RunConfiguration{} + remainingFlakyFailures = []v1.Test{} + retries = 0 + flakyRetries = 2 + nonFlakyRetries = 1 + }) + + Context("when QuarantinedTestRetries is 0", func() { + BeforeEach(func() { + cfg.QuarantinedTestRetries = 0 + apiConfig.QuarantinedTests = []backend.QuarantinedTest{ + { + Test: backend.Test{ + CompositeIdentifier: "quarantined-test -captain- /path/to/file.test", + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, + } + }) + + It("filters out quarantined tests when retries is 0", func() { + filter := service.CreateRetryFilter(apiConfig, remainingFlakyFailures, retries, flakyRetries, + nonFlakyRetries, 0) + quarantinedTestName := "quarantined-test" + nonQuarantinedTestName := "normal-test" + + quarantinedTest := v1.Test{ + ID: &quarantinedTestName, + Name: quarantinedTestName, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + } + + nonQuarantinedTest := v1.Test{ + ID: &nonQuarantinedTestName, + Name: nonQuarantinedTestName, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + } + + Expect(filter(quarantinedTest)).To(BeFalse(), "quarantined test should be filtered out when retries is 0") + Expect(filter(nonQuarantinedTest)).To(BeTrue(), "non-quarantined test should not be filtered out") + }) + + It("allows non-failing tests to pass through", func() { + filter := service.CreateRetryFilter(apiConfig, remainingFlakyFailures, retries, flakyRetries, + nonFlakyRetries, 0) + + successfulTestName := "successful-test" + successfulTest := v1.Test{ + ID: &successfulTestName, + Name: successfulTestName, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewSuccessfulTestStatus(), + }, + } + + Expect(filter(successfulTest)).To(BeFalse(), "successful test should be filtered out") + }) + }) + + Context("when QuarantinedTestRetries is 2", func() { + BeforeEach(func() { + cfg.QuarantinedTestRetries = 2 + apiConfig.QuarantinedTests = []backend.QuarantinedTest{ + { + Test: backend.Test{ + CompositeIdentifier: "quarantined-test -captain- /path/to/file.test", + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, + } + }) + + It("allows quarantined tests to be retried up to the specified limit", func() { + // Test with retries = 0 (should allow retry) + filter := service.CreateRetryFilter(apiConfig, remainingFlakyFailures, 0, flakyRetries, + nonFlakyRetries, 2) + quarantinedTestName := "quarantined-test" + quarantinedTest := v1.Test{ + ID: &quarantinedTestName, + Name: quarantinedTestName, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + } + Expect(filter(quarantinedTest)).To(BeTrue(), "quarantined test should be retried when retries < limit") + + // Test with retries = 2 (should not allow retry) + filter = service.CreateRetryFilter(apiConfig, remainingFlakyFailures, 2, flakyRetries, + nonFlakyRetries, 2) + Expect(filter(quarantinedTest)).To(BeFalse(), "quarantined test should not be retried when retries >= limit") + }) + }) + + Context("when QuarantinedTestRetries is -1 (default)", func() { + BeforeEach(func() { + cfg.QuarantinedTestRetries = -1 + apiConfig.QuarantinedTests = []backend.QuarantinedTest{ + { + Test: backend.Test{ + CompositeIdentifier: "quarantined-test -captain- /path/to/file.test", + IdentityComponents: []string{"description", "file"}, + StrictIdentity: true, + }, + }, + } + }) + + It("does not filter out quarantined tests when retries is -1 (default)", func() { + filter := service.CreateRetryFilter(apiConfig, remainingFlakyFailures, retries, flakyRetries, + nonFlakyRetries, -1) + + quarantinedTestName := "quarantined-test" + quarantinedTest := v1.Test{ + ID: &quarantinedTestName, + Name: quarantinedTestName, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + } + + Expect(filter(quarantinedTest)).To(BeTrue(), + "quarantined test should not be filtered out when retries is -1 (default)") + }) + }) + + Context("when retry limits are exceeded", func() { + BeforeEach(func() { + cfg.QuarantinedTestRetries = -1 + retries = 2 + flakyRetries = 1 + nonFlakyRetries = 1 + }) + + It("filters out flaky tests when flaky retry limit is exceeded", func() { + flakyTestName := "flaky-test" + remainingFlakyFailures = []v1.Test{ + { + ID: &flakyTestName, + Name: flakyTestName, + Location: &v1.Location{File: "/path/to/file.test"}, + }, + } + + filter := service.CreateRetryFilter(apiConfig, remainingFlakyFailures, retries, flakyRetries, + nonFlakyRetries, -1) + + flakyTest := v1.Test{ + ID: &flakyTestName, + Name: flakyTestName, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + } + + Expect(filter(flakyTest)).To(BeFalse(), "flaky test should be filtered out when retry limit exceeded") + }) + + It("filters out non-flaky tests when non-flaky retry limit is exceeded", func() { + filter := service.CreateRetryFilter(apiConfig, remainingFlakyFailures, retries, flakyRetries, + nonFlakyRetries, -1) + + nonFlakyTestName := "non-flaky-test" + nonFlakyTest := v1.Test{ + ID: &nonFlakyTestName, + Name: nonFlakyTestName, + Location: &v1.Location{File: "/path/to/file.test"}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + } + + Expect(filter(nonFlakyTest)).To(BeFalse(), "non-flaky test should be filtered out when retry limit exceeded") + }) + }) + }) +}) diff --git a/internal/captain/cli/service.go b/internal/captain/cli/service.go new file mode 100644 index 0000000..0fa7f2e --- /dev/null +++ b/internal/captain/cli/service.go @@ -0,0 +1,56 @@ +// Package cli holds the main business logic in our CLI. This is mainly: +// 1. Triggering the right API calls based on the provided input parameters. +// 2. User-friendly logging +// However, this package _does not_ implement the actual terminal UI. That part is handled by `cmd/captain`. +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.uber.org/zap" + + "github.com/rwx-cloud/cli/internal/captain/backend" + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/fs" + "github.com/rwx-cloud/cli/internal/captain/parsing" +) + +// Service is the main CLI service. +type Service struct { + API backend.Client + Log *zap.SugaredLogger + FileSystem fs.FileSystem + TaskRunner TaskRunner + ParseConfig parsing.Config +} + +type contextKey string + +var configKey = contextKey("captainService") + +func GetService(cmd *cobra.Command) (Service, error) { + val := cmd.Context().Value(configKey) + if val == nil { + return Service{}, errors.NewInternalError( + "Tried to fetch config from the command but it wasn't set. This should never happen!") + } + + service, ok := val.(Service) + if !ok { + return Service{}, errors.NewInternalError( + "Tried to fetch config from the command but it was of the wrong type. This should never happen!") + } + + return service, nil +} + +func SetService(cmd *cobra.Command, service Service) error { + if _, err := GetService(cmd); err == nil { + return errors.NewInternalError("Tried to set config on the command but it was already set. This should never happen!") + } + + ctx := context.WithValue(cmd.Context(), configKey, service) + cmd.SetContext(ctx) + return nil +} diff --git a/internal/captain/cli/utils.go b/internal/captain/cli/utils.go new file mode 100644 index 0000000..1efa79c --- /dev/null +++ b/internal/captain/cli/utils.go @@ -0,0 +1,200 @@ +package cli + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/fs" +) + +type IntermediateArtifactStorage struct { + basePath string + commandID string + fs fs.FileSystem + retryID string + workingDir string +} + +func (s Service) NewIntermediateArtifactStorage(path string) (*IntermediateArtifactStorage, error) { + wd, err := s.FileSystem.Getwd() + if err != nil { + return nil, errors.WithStack(err) + } + + ias := &IntermediateArtifactStorage{ + basePath: path, + fs: s.FileSystem, + workingDir: wd, + retryID: "original-attempt", + } + + if path == "" { + path, err := s.FileSystem.MkdirTemp("", "rwx-test") + if err != nil { + return nil, errors.WithStack(err) + } + + ias.basePath = path + + return ias, nil + } + + info, err := s.FileSystem.Stat(path) + if errors.Is(err, os.ErrNotExist) { + err = s.FileSystem.MkdirAll(path, 0o750) + } + if err != nil { + return nil, errors.WithStack(err) + } + + if info != nil && !info.IsDir() { + return nil, errors.NewConfigurationError( + "Intermediate artifacts path is not a directory", + fmt.Sprintf( + "You specified %q as the path for intermediate artifacts. However, this appears to be a file, not a "+ + "directory.", + path, + ), + "Please make sure that the intermediate artifacts path points to new path or an existing directory. rwx test "+ + "will create a directory in case the path doesn't exist yet.", + ) + } + + return ias, nil +} + +func (ias *IntermediateArtifactStorage) moveTestResults(artifacts []string) error { + attemptPath := filepath.Join(ias.basePath, ias.retryID) + if ias.commandID != "" { + attemptPath = filepath.Join(attemptPath, ias.commandID) + } + + for _, artifact := range artifacts { + dir, filename := filepath.Split(artifact) + + var targetDir string + if filepath.IsAbs(dir) { + // For absolute paths outside working directory, preserve full structure + relDir, err := filepath.Rel(ias.workingDir, dir) + if err != nil || !fs.IsLocal(relDir) { + // Path is outside working directory, use full absolute path + targetDir = filepath.Join(attemptPath, strings.TrimPrefix(dir, "/")) + } else { + // Path is inside working directory, use __rwx_working_directory prefix + targetDir = filepath.Join(attemptPath, "__rwx_working_directory", relDir) + } + } else { + // Relative paths go under __rwx_working_directory + targetDir = filepath.Join(attemptPath, "__rwx_working_directory", dir) + } + + if err := ias.fs.MkdirAll(targetDir, 0o750); err != nil { + return errors.WithStack(err) + } + + if err := ias.moveFile(artifact, filepath.Join(targetDir, filename)); err != nil { + return errors.WithStack(err) + } + } + + return nil +} + +func (ias *IntermediateArtifactStorage) MoveAdditionalArtifacts(artifactPatterns []string) error { + if len(artifactPatterns) == 0 { + return nil + } + + artifacts, err := ias.fs.GlobMany(artifactPatterns) + if err != nil { + return errors.WithStack(err) + } + + attemptPath := filepath.Join(ias.basePath, ias.retryID) + if ias.commandID != "" { + attemptPath = filepath.Join(attemptPath, ias.commandID) + } + + for _, artifact := range artifacts { + dir, filename := filepath.Split(artifact) + + var targetDir string + if filepath.IsAbs(dir) { + relDir, err := filepath.Rel(ias.workingDir, dir) + if err != nil || !fs.IsLocal(relDir) { + targetDir = filepath.Join(attemptPath, strings.TrimPrefix(dir, "/")) + } else { + targetDir = filepath.Join(attemptPath, "__rwx_working_directory", relDir) + } + } else { + targetDir = filepath.Join(attemptPath, "__rwx_working_directory", dir) + } + + if err := ias.fs.MkdirAll(targetDir, 0o750); err != nil { + return errors.WithStack(err) + } + + if err := ias.moveFile(artifact, filepath.Join(targetDir, filename)); err != nil { + return errors.WithStack(err) + } + } + + return nil +} + +func (ias *IntermediateArtifactStorage) moveFile(srcPath, dstPath string) error { + // Renaming only works if both paths are on the same file-system. We'll fall back to + // copy & delete instead if this is not the case. + if err := ias.fs.Rename(srcPath, dstPath); err == nil { + return nil + } + + srcFile, err := ias.fs.Open(srcPath) + if err != nil { + return errors.WithStack(err) + } + + dstFile, err := ias.fs.Create(dstPath) + if err != nil { + return errors.WithStack(err) + } + defer func() { + _ = dstFile.Close() + }() + + if _, err = io.Copy(dstFile, srcFile); err != nil { + _ = srcFile.Close() + return errors.WithStack(err) + } + + if err = dstFile.Sync(); err != nil { + _ = srcFile.Close() + return errors.WithStack(err) + } + + if err = srcFile.Close(); err != nil { + return errors.WithStack(err) + } + + if err = ias.fs.Remove(srcPath); err != nil { + return errors.WithStack(err) + } + + return nil +} + +func (ias *IntermediateArtifactStorage) delete() error { + return errors.WithStack(ias.fs.RemoveAll(ias.basePath)) +} + +func (ias *IntermediateArtifactStorage) SetCommandID(n int) { + ias.commandID = fmt.Sprintf("command-%d", n) +} + +func (ias *IntermediateArtifactStorage) SetRetryID(n int) { + ias.retryID = fmt.Sprintf("retry-%d", n) +} diff --git a/internal/captain/cli/utils_test.go b/internal/captain/cli/utils_test.go new file mode 100644 index 0000000..a1ce451 --- /dev/null +++ b/internal/captain/cli/utils_test.go @@ -0,0 +1,347 @@ +package cli_test + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + + "github.com/rwx-cloud/cli/internal/captain/cli" + "github.com/rwx-cloud/cli/internal/captain/fs" + "github.com/rwx-cloud/cli/internal/captain/mocks" +) + +var ( + errCrossDeviceLink = errors.New("cross-device link") + errGlob = errors.New("glob error") + errMkdir = errors.New("mkdir error") + errOpen = errors.New("open error") + errGetwd = errors.New("getwd error") +) + +var _ = ginkgo.Describe("IntermediateArtifactStorage", func() { + var ( + service cli.Service + mockFS *mocks.FileSystem + workingDir string + basePath string + ias *cli.IntermediateArtifactStorage + ) + + ginkgo.BeforeEach(func() { + workingDir = "/working/dir" + basePath = fmt.Sprintf("intermediate-results-%d", ginkgo.GinkgoRandomSeed()) + + mockFS = new(mocks.FileSystem) + service = cli.Service{ + FileSystem: mockFS, + } + + mockFS.MockGetwd = func() (string, error) { + return workingDir, nil + } + + mockFS.MockStat = func(name string) (os.FileInfo, error) { + if name == basePath { + return nil, os.ErrNotExist + } + return nil, os.ErrNotExist + } + + mockFS.MockMkdirAll = func(_ string, _ os.FileMode) error { + return nil + } + + var err error + ias, err = service.NewIntermediateArtifactStorage(basePath) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + + ginkgo.Describe("MoveAdditionalArtifacts", func() { + ginkgo.Context("when no artifact patterns are provided", func() { + ginkgo.It("returns nil without calling filesystem operations", func() { + err := ias.MoveAdditionalArtifacts([]string{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + }) + + ginkgo.Context("when artifact patterns are provided", func() { + var ( + patterns []string + artifacts []string + ) + + ginkgo.BeforeEach(func() { + patterns = []string{"coverage/**/*", "reports/**/*", "/tmp/external/**/*"} + artifacts = []string{ + "coverage/lcov.info", + "reports/junit.xml", + "/tmp/external/debug.log", + "/working/dir/subdir/artifact.txt", + } + + mockFS.MockGlobMany = func(p []string) ([]string, error) { + gomega.Expect(p).To(gomega.Equal(patterns)) + return artifacts, nil + } + + mockFS.MockRename = func(_, _ string) error { + return nil + } + }) + + ginkgo.It("moves artifacts with correct path transformations", func() { + var renamedPaths [][]string + mockFS.MockRename = func(oldpath, newpath string) error { + renamedPaths = append(renamedPaths, []string{oldpath, newpath}) + return nil + } + + err := ias.MoveAdditionalArtifacts(patterns) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + gomega.Expect(renamedPaths).To(gomega.HaveLen(4)) + + // Relative path: coverage/lcov.info -> __rwx_working_directory/coverage/lcov.info + gomega.Expect(renamedPaths[0][0]).To(gomega.Equal("coverage/lcov.info")) + expectedPath0 := filepath.Join(basePath, "original-attempt", "__rwx_working_directory", "coverage", "lcov.info") + gomega.Expect(renamedPaths[0][1]).To(gomega.Equal(expectedPath0)) + + // Relative path: reports/junit.xml -> __rwx_working_directory/reports/junit.xml + gomega.Expect(renamedPaths[1][0]).To(gomega.Equal("reports/junit.xml")) + expectedPath1 := filepath.Join(basePath, "original-attempt", "__rwx_working_directory", "reports", "junit.xml") + gomega.Expect(renamedPaths[1][1]).To(gomega.Equal(expectedPath1)) + + // Absolute path outside working directory: /tmp/external/debug.log -> tmp/external/debug.log + gomega.Expect(renamedPaths[2][0]).To(gomega.Equal("/tmp/external/debug.log")) + expectedPath2 := filepath.Join(basePath, "original-attempt", "tmp", "external", "debug.log") + gomega.Expect(renamedPaths[2][1]).To(gomega.Equal(expectedPath2)) + + // Absolute path inside working directory: /working/dir/subdir/artifact.txt -> __rwx_working_directory/subdir/artifact.txt + gomega.Expect(renamedPaths[3][0]).To(gomega.Equal("/working/dir/subdir/artifact.txt")) + expectedPath3 := filepath.Join(basePath, "original-attempt", + "__rwx_working_directory", "subdir", "artifact.txt") + gomega.Expect(renamedPaths[3][1]).To(gomega.Equal(expectedPath3)) + }) + + ginkgo.It("creates necessary directories before moving files", func() { + var createdDirs []string + mockFS.MockMkdirAll = func(path string, _ os.FileMode) error { + createdDirs = append(createdDirs, path) + return nil + } + + err := ias.MoveAdditionalArtifacts(patterns) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + gomega.Expect(createdDirs).To(gomega.HaveLen(4)) + expectedDir0 := filepath.Join(basePath, "original-attempt", "__rwx_working_directory", "coverage") + gomega.Expect(createdDirs).To(gomega.ContainElement(expectedDir0)) + expectedDir1 := filepath.Join(basePath, "original-attempt", "__rwx_working_directory", "reports") + gomega.Expect(createdDirs).To(gomega.ContainElement(expectedDir1)) + expectedDir2 := filepath.Join(basePath, "original-attempt", "tmp", "external") + gomega.Expect(createdDirs).To(gomega.ContainElement(expectedDir2)) + expectedDir3 := filepath.Join(basePath, "original-attempt", "__rwx_working_directory", "subdir") + gomega.Expect(createdDirs).To(gomega.ContainElement(expectedDir3)) + }) + + ginkgo.It("handles retry attempts by updating the retry ID", func() { + ias.SetRetryID(2) + + var renamedPaths [][]string + mockFS.MockRename = func(oldpath, newpath string) error { + renamedPaths = append(renamedPaths, []string{oldpath, newpath}) + return nil + } + + err := ias.MoveAdditionalArtifacts(patterns) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + gomega.Expect(renamedPaths[0][1]).To(gomega.ContainSubstring("retry-2")) + gomega.Expect(renamedPaths[0][1]).NotTo(gomega.ContainSubstring("original-attempt")) + }) + + ginkgo.It("handles command ID by including it in the path", func() { + ias.SetCommandID(3) + + var renamedPaths [][]string + mockFS.MockRename = func(oldpath, newpath string) error { + renamedPaths = append(renamedPaths, []string{oldpath, newpath}) + return nil + } + + err := ias.MoveAdditionalArtifacts(patterns) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + gomega.Expect(renamedPaths[0][1]).To(gomega.ContainSubstring("command-3")) + }) + + ginkgo.It("falls back to copy+delete when rename fails", func() { + mockFS.MockRename = func(_, _ string) error { + return errCrossDeviceLink + } + + var openedFiles []string + var createdFiles []string + var removedFiles []string + + mockFS.MockOpen = func(path string) (fs.File, error) { + openedFiles = append(openedFiles, path) + return &mocks.File{ + Builder: new(strings.Builder), + Reader: strings.NewReader("test content"), + }, nil + } + + mockFS.MockCreate = func(name string) (fs.File, error) { + createdFiles = append(createdFiles, name) + return &mocks.File{ + Builder: new(strings.Builder), + Reader: strings.NewReader(""), + }, nil + } + + mockFS.MockRemove = func(name string) error { + removedFiles = append(removedFiles, name) + return nil + } + + err := ias.MoveAdditionalArtifacts(patterns) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + gomega.Expect(openedFiles).To(gomega.HaveLen(4)) + gomega.Expect(openedFiles).To(gomega.ContainElement("coverage/lcov.info")) + gomega.Expect(openedFiles).To(gomega.ContainElement("reports/junit.xml")) + gomega.Expect(openedFiles).To(gomega.ContainElement("/tmp/external/debug.log")) + gomega.Expect(openedFiles).To(gomega.ContainElement("/working/dir/subdir/artifact.txt")) + + gomega.Expect(createdFiles).To(gomega.HaveLen(4)) + + gomega.Expect(removedFiles).To(gomega.HaveLen(4)) + gomega.Expect(removedFiles).To(gomega.ContainElement("coverage/lcov.info")) + gomega.Expect(removedFiles).To(gomega.ContainElement("reports/junit.xml")) + gomega.Expect(removedFiles).To(gomega.ContainElement("/tmp/external/debug.log")) + gomega.Expect(removedFiles).To(gomega.ContainElement("/working/dir/subdir/artifact.txt")) + }) + }) + + ginkgo.Context("when glob operation fails", func() { + ginkgo.It("returns the error", func() { + mockFS.MockGlobMany = func(_ []string) ([]string, error) { + return nil, errGlob + } + + err := ias.MoveAdditionalArtifacts([]string{"coverage/**/*"}) + gomega.Expect(err).To(gomega.HaveOccurred()) + gomega.Expect(err.Error()).To(gomega.ContainSubstring("glob error")) + }) + }) + + ginkgo.Context("when directory creation fails", func() { + ginkgo.It("returns the error", func() { + mockFS.MockGlobMany = func(_ []string) ([]string, error) { + return []string{"coverage/lcov.info"}, nil + } + + mockFS.MockMkdirAll = func(_ string, _ os.FileMode) error { + return errMkdir + } + + err := ias.MoveAdditionalArtifacts([]string{"coverage/**/*"}) + gomega.Expect(err).To(gomega.HaveOccurred()) + gomega.Expect(err.Error()).To(gomega.ContainSubstring("mkdir error")) + }) + }) + + ginkgo.Context("when file move operation fails completely", func() { + ginkgo.It("returns the error", func() { + mockFS.MockGlobMany = func(_ []string) ([]string, error) { + return []string{"coverage/lcov.info"}, nil + } + + mockFS.MockMkdirAll = func(_ string, _ os.FileMode) error { + return nil + } + + mockFS.MockRename = func(_, _ string) error { + return errCrossDeviceLink + } + + mockFS.MockOpen = func(_ string) (fs.File, error) { + return nil, errOpen + } + + err := ias.MoveAdditionalArtifacts([]string{"coverage/**/*"}) + gomega.Expect(err).To(gomega.HaveOccurred()) + gomega.Expect(err.Error()).To(gomega.ContainSubstring("open error")) + }) + }) + }) + + ginkgo.Describe("NewIntermediateArtifactStorage", func() { + ginkgo.Context("when path is provided", func() { + ginkgo.It("creates storage with the provided path", func() { + providedPath := "/custom/artifacts/path" + + mockFS.MockStat = func(name string) (os.FileInfo, error) { + gomega.Expect(name).To(gomega.Equal(providedPath)) + return nil, os.ErrNotExist + } + + mockFS.MockMkdirAll = func(path string, _ os.FileMode) error { + gomega.Expect(path).To(gomega.Equal(providedPath)) + return nil + } + + storage, err := service.NewIntermediateArtifactStorage(providedPath) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(storage).NotTo(gomega.BeNil()) + }) + + ginkgo.It("validates that the path is not a file", func() { + providedPath := "/custom/artifacts/path" + + mockFileInfo := mocks.FileInfo{Dir: false} + + mockFS.MockStat = func(_ string) (os.FileInfo, error) { + return mockFileInfo, nil + } + + _, err := service.NewIntermediateArtifactStorage(providedPath) + gomega.Expect(err).To(gomega.HaveOccurred()) + gomega.Expect(err.Error()).To(gomega.ContainSubstring("Intermediate artifacts path is not a directory")) + }) + }) + + ginkgo.Context("when no path is provided", func() { + ginkgo.It("creates a temporary directory", func() { + tempDir := "/tmp/captain123" + + mockFS.MockMkdirTemp = func(_ string, pattern string) (string, error) { + gomega.Expect(pattern).To(gomega.Equal("rwx-test")) + return tempDir, nil + } + + storage, err := service.NewIntermediateArtifactStorage("") + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(storage).NotTo(gomega.BeNil()) + }) + }) + + ginkgo.Context("when working directory cannot be determined", func() { + ginkgo.It("returns an error", func() { + mockFS.MockGetwd = func() (string, error) { + return "", errGetwd + } + + _, err := service.NewIntermediateArtifactStorage(basePath) + gomega.Expect(err).To(gomega.HaveOccurred()) + gomega.Expect(err.Error()).To(gomega.ContainSubstring("getwd error")) + }) + }) + }) +}) diff --git a/internal/captain/config/config.go b/internal/captain/config/config.go new file mode 100644 index 0000000..47611d7 --- /dev/null +++ b/internal/captain/config/config.go @@ -0,0 +1,12 @@ +package config + +import "fmt" + +type PartitionNodes struct { + Total int + Index int +} + +func (pn PartitionNodes) String() string { + return fmt.Sprintf("%d/%d", pn.Index, pn.Total) +} diff --git a/internal/captain/errors/errors.go b/internal/captain/errors/errors.go new file mode 100644 index 0000000..759fa64 --- /dev/null +++ b/internal/captain/errors/errors.go @@ -0,0 +1,194 @@ +// Package errors is our internal errors package. It should be used in place of the standard "errors" package. +// This package ensures that all errors have a correct category & collect stack-traces. +package errors + +import "github.com/pkg/errors" + +// ConfigurationError represent a configuration error. When used, it should ideally also point towards the configuration +// value that caused this error to occur. +type ConfigurationError struct { + desc string + err error + fix string +} + +func (e ConfigurationError) Error() string { + return e.err.Error() +} + +func (e ConfigurationError) Description() string { + return e.desc +} + +func (e ConfigurationError) Resolution() string { + return e.fix +} + +func (e ConfigurationError) Type() string { + return "Invalid configuration" +} + +// NewConfigurationError returns a new ConfigurationError +func NewConfigurationError(title, desc, fix string) error { + return WithStack(ConfigurationError{err: errors.New(title), desc: desc, fix: fix}) +} + +// AsConfigurationError checks whether the error is a configuration error +func AsConfigurationError(err error) (ConfigurationError, bool) { + var e ConfigurationError + ok := As(err, &e) + return e, ok +} + +// ExecutionError is an error that was encountered during the execution of a different task. Specifically, this is being +// used with the `captain run` command, which executes a build- or test-suite as a sub-process. +// Execution errors can store an optional error-code. +type ExecutionError struct { + E error + Code int +} + +func (e ExecutionError) Error() string { + return e.E.Error() +} + +// NewExecutionError returns a new ExecutionError +func NewExecutionError(code int, msg string, a ...any) error { + return WithStack(ExecutionError{Code: code, E: errors.Errorf(msg, a...)}) +} + +// AsExecutionError checks whether the error is an execution error. +func AsExecutionError(err error) (ExecutionError, bool) { + var e ExecutionError + ok := As(err, &e) + return e, ok +} + +// InputError is an error caused by user input +type InputError struct { + E error +} + +func (e InputError) Error() string { + return e.E.Error() +} + +// InputError returns a new InputError +func NewInputError(msg string, a ...any) error { + return WithStack(InputError{errors.Errorf(msg, a...)}) +} + +// AsInputError checks whether the error is an input error +func AsInputError(err error) (InputError, bool) { + var e InputError + ok := As(err, &e) + return e, ok +} + +// InternalError is an internal error. This error type should only be used if an end-user cannot act upon it and would +// need to reach out to us for support. +type InternalError struct { + E error +} + +func (e InternalError) Error() string { + return e.E.Error() +} + +// InternalError returns a new InternalError +func NewInternalError(msg string, a ...any) error { + return WithStack(InternalError{errors.Errorf(msg, a...)}) +} + +// AsInternalError checks whether the error is an internal error +func AsInternalError(err error) (InternalError, bool) { + var e InternalError + ok := As(err, &e) + return e, ok +} + +// SystemError is returned when the CLI encountered a system error. This is most likely either an error during file read +// or a network error. +type SystemError struct { + E error +} + +func (e SystemError) Error() string { + return e.E.Error() +} + +// SystemError returns a new SystemError +func NewSystemError(msg string, a ...any) error { + return WithStack(SystemError{errors.Errorf(msg, a...)}) +} + +// AsSystemError checks whether the error is a system error +func AsSystemError(err error) (SystemError, bool) { + var e SystemError + ok := As(err, &e) + return e, ok +} + +// RetryError is an error related to retries. This error type is used in conjunction with the --fail-on-misconfigured-retry flag. +type RetryError struct { + E error +} + +func (e RetryError) Error() string { + return e.E.Error() +} + +// RetryError returns a new RetryError +func NewRetryError(msg string, a ...any) error { + return WithStack(RetryError{errors.Errorf(msg, a...)}) +} + +// AsRetryError checks whether the error is a retry error +func AsRetryError(err error) (RetryError, bool) { + var e RetryError + ok := As(err, &e) + return e, ok +} + +// CacheError is returned when the CLI encountered a cache error. It's intended that the application catches these errors and +// handles them gracefully +type CacheError struct { + E error +} + +func (e CacheError) Error() string { + return e.E.Error() +} + +// CacheError returns a new CacheError +func NewCacheError(msg string, a ...any) error { + return WithStack(CacheError{errors.Errorf(msg, a...)}) +} + +// AsCacheError checks whether the error is a cache error +func AsCacheError(err error) (CacheError, bool) { + var e CacheError + ok := As(err, &e) + return e, ok +} + +// DuplicateTestIDError is an error caused by duplicate test IDs +type DuplicateTestIDError struct { + E error +} + +func (e DuplicateTestIDError) Error() string { + return e.E.Error() +} + +// DuplicateTestIDError returns a new DuplicateTestIDError +func NewDuplicateTestIDError(msg string, a ...any) error { + return WithStack(DuplicateTestIDError{errors.Errorf(msg, a...)}) +} + +// AsDuplicateTestIDError checks whether the error is a duplicate test ID error +func AsDuplicateTestIDError(err error) (DuplicateTestIDError, bool) { + var e DuplicateTestIDError + ok := As(err, &e) + return e, ok +} diff --git a/internal/captain/errors/errors_suite_test.go b/internal/captain/errors/errors_suite_test.go new file mode 100644 index 0000000..77e9207 --- /dev/null +++ b/internal/captain/errors/errors_suite_test.go @@ -0,0 +1,15 @@ +package errors_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestErrors(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "Errors Suite") +} diff --git a/internal/captain/errors/errors_test.go b/internal/captain/errors/errors_test.go new file mode 100644 index 0000000..6bcc4d5 --- /dev/null +++ b/internal/captain/errors/errors_test.go @@ -0,0 +1,102 @@ +package errors_test + +import ( + "fmt" + + "github.com/rwx-cloud/cli/internal/captain/errors" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Errors", func() { + Describe("ConfigurationError", func() { + It("behaves like an error", func() { + err := errors.NewConfigurationError(fmt.Sprintf("some error %v", "some value"), "", "") + Expect(err.Error()).To(Equal("some error some value")) + Expect(fmt.Sprintf("%+v", err)).To(ContainSubstring("/errors_test.go")) + + configErr, ok := errors.AsConfigurationError(err) + + Expect(ok).To(Equal(true)) + Expect(configErr).To(Equal(errors.Unwrap(err))) + + internalErr, ok := errors.AsInternalError(err) + + Expect(ok).To(Equal(false)) + Expect(internalErr.E).To(BeNil()) + }) + }) + + Describe("ExecutionError", func() { + It("behaves like an error", func() { + err := errors.NewExecutionError(2, "some error %v", "some value") + Expect(err.Error()).To(Equal("some error some value")) + Expect(fmt.Sprintf("%+v", err)).To(ContainSubstring("/errors_test.go")) + + executionErr, ok := errors.AsExecutionError(err) + + Expect(ok).To(Equal(true)) + Expect(executionErr).To(Equal(errors.Unwrap(err))) + + internalErr, ok := errors.AsInternalError(err) + + Expect(ok).To(Equal(false)) + Expect(internalErr.E).To(BeNil()) + }) + }) + + Describe("InputError", func() { + It("behaves like an error", func() { + err := errors.NewInputError("some error %v", "some value") + Expect(err.Error()).To(Equal("some error some value")) + Expect(fmt.Sprintf("%+v", err)).To(ContainSubstring("/errors_test.go")) + + inputErr, ok := errors.AsInputError(err) + + Expect(ok).To(Equal(true)) + Expect(inputErr).To(Equal(errors.Unwrap(err))) + + internalErr, ok := errors.AsInternalError(err) + + Expect(ok).To(Equal(false)) + Expect(internalErr.E).To(BeNil()) + }) + }) + + Describe("InternalError", func() { + It("behaves like an error", func() { + err := errors.NewInternalError("some error %v", "some value") + Expect(err.Error()).To(Equal("some error some value")) + Expect(fmt.Sprintf("%+v", err)).To(ContainSubstring("/errors_test.go")) + + internalErr, ok := errors.AsInternalError(err) + + Expect(ok).To(Equal(true)) + Expect(internalErr).To(Equal(errors.Unwrap(err))) + + systemErr, ok := errors.AsSystemError(err) + + Expect(ok).To(Equal(false)) + Expect(systemErr.E).To(BeNil()) + }) + }) + + Describe("SystemError", func() { + It("behaves like an error", func() { + err := errors.NewSystemError("some error %v", "some value") + Expect(err.Error()).To(Equal("some error some value")) + Expect(fmt.Sprintf("%+v", err)).To(ContainSubstring("/errors_test.go")) + + systemErr, ok := errors.AsSystemError(err) + + Expect(ok).To(Equal(true)) + Expect(systemErr).To(Equal(errors.Unwrap(err))) + + internalErr, ok := errors.AsInternalError(err) + + Expect(ok).To(Equal(false)) + Expect(internalErr.E).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/errors/template.go b/internal/captain/errors/template.go new file mode 100644 index 0000000..80016d7 --- /dev/null +++ b/internal/captain/errors/template.go @@ -0,0 +1,39 @@ +package errors + +const errorTemplate = `{{.Type}}: {{.Title}} + +{{.Description}} +{{.Resolution}} + +If issues persist please reach out to support@rwx.com for assistance. +` + +type detailedError interface { + Description() string + Error() string + Resolution() string + Type() string +} + +type templateVariables struct { + Title string + Type string + Description string + Resolution string +} + +func (t templateVariables) Validate() error { + if t.Title == "" { + return NewInternalError("error message is missing a title") + } + + if t.Type == "" { + return NewInternalError("error message is missing a type") + } + + if t.Description == "" { + return NewInternalError("error message is missing a Description") + } + + return nil +} diff --git a/internal/captain/errors/utils.go b/internal/captain/errors/utils.go new file mode 100644 index 0000000..7157f75 --- /dev/null +++ b/internal/captain/errors/utils.go @@ -0,0 +1,79 @@ +package errors + +import ( + "strings" + "text/template" + + "github.com/mitchellh/go-wordwrap" + "github.com/pkg/errors" +) + +// As is a wrapper around the standard library `errors.As` +func As(err error, target any) bool { + return errors.As(err, target) +} + +// decorate returns a "pretty-printed" error message for end-users. +func decorate(err detailedError) string { + t, parserErr := template.New("error").Parse(errorTemplate) + if parserErr != nil { + // TODO: Don't discard parserErr here + return err.Error() + } + + vars := templateVariables{ + Title: err.Error(), + Description: wordwrap.WrapString(err.Description(), 80), + Resolution: wordwrap.WrapString(err.Resolution(), 80), + Type: err.Type(), + } + + if validationErr := vars.Validate(); validationErr != nil { + // TODO: Don't discard validationErr here + return err.Error() + } + + var buf strings.Builder + if renderErr := t.Execute(&buf, vars); renderErr != nil { + // TODO: Don't discard renderErr here + return err.Error() + } + + return buf.String() +} + +// Is is a wrapper around the standard library `errors.Is` +func Is(err, target error) bool { + return errors.Is(err, target) +} + +// WithDecoration returns a generic (i.e. unwrapped / no stack-trace) error, but decorated +func WithDecoration(e error) error { + var err detailedError + + if ok := As(e, &err); !ok { + return e + } + + return errors.New(decorate(err)) +} + +// Adds a stack trace to an error without doing anything further +func WithStack(err error) error { + return errors.WithStack(err) +} + +// Wrap is similar to 'WithStack', but adds a message to the error +func Wrap(err error, msg string) error { + return errors.Wrap(err, msg) +} + +// Wrapf is similar to 'Wrap', but formats the message +func Wrapf(err error, msg string, a ...any) error { + return errors.Wrapf(err, msg, a...) +} + +// Unwraps err one level +func Unwrap(err error) error { + return errors.Unwrap(err) +} diff --git a/internal/captain/errors/utils_test.go b/internal/captain/errors/utils_test.go new file mode 100644 index 0000000..e4bd373 --- /dev/null +++ b/internal/captain/errors/utils_test.go @@ -0,0 +1,62 @@ +package errors_test + +import ( + "fmt" + + pkgerrors "github.com/pkg/errors" + + "github.com/rwx-cloud/cli/internal/captain/errors" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Utils", func() { + Describe("WithStack", func() { + It("wraps an error without a message", func() { + err := pkgerrors.New("some error") + wrapped := errors.WithStack(err) + Expect(wrapped.Error()).To(Equal("some error")) + Expect(wrapped).NotTo(Equal(err)) + Expect(fmt.Sprintf("%+v", wrapped)).To(ContainSubstring("/utils_test.go")) + + var errPkg error + ok := errors.As(err, &errPkg) + + Expect(ok).To(Equal(true)) + Expect(errPkg).To(Equal(err)) + }) + }) + + Describe("Wrap", func() { + It("wraps an error with a message", func() { + err := pkgerrors.New("some error") + wrapped := errors.Wrap(err, "some prefix") + Expect(wrapped.Error()).To(Equal("some prefix: some error")) + Expect(wrapped).NotTo(Equal(err)) + Expect(fmt.Sprintf("%+v", wrapped)).To(ContainSubstring("/utils_test.go")) + + var errPkg error + ok := errors.As(err, &errPkg) + + Expect(ok).To(Equal(true)) + Expect(errPkg).To(Equal(err)) + }) + }) + + Describe("Wrapf", func() { + It("wraps an error with a formatted message", func() { + err := pkgerrors.New("some error") + wrapped := errors.Wrapf(err, "some prefix %v", "formatted") + Expect(wrapped.Error()).To(Equal("some prefix formatted: some error")) + Expect(wrapped).NotTo(Equal(err)) + Expect(fmt.Sprintf("%+v", wrapped)).To(ContainSubstring("/utils_test.go")) + + var errPkg error + ok := errors.As(err, &errPkg) + + Expect(ok).To(Equal(true)) + Expect(errPkg).To(Equal(err)) + }) + }) +}) diff --git a/internal/captain/exec/command.go b/internal/captain/exec/command.go new file mode 100644 index 0000000..4cf2fba --- /dev/null +++ b/internal/captain/exec/command.go @@ -0,0 +1,8 @@ +package exec + +// Command is a generic interface that represents a command that is being executed. This is modelled after the default +// `exec.Cmd` from the `os/exec` package. +type Command interface { + Start() error + Wait() error +} diff --git a/internal/captain/exec/config.go b/internal/captain/exec/config.go new file mode 100644 index 0000000..6303c62 --- /dev/null +++ b/internal/captain/exec/config.go @@ -0,0 +1,12 @@ +package exec + +import "io" + +// CommandConfig configures a command for execution +type CommandConfig struct { + Args []string + Env []string + Name string + Stderr io.Writer + Stdout io.Writer +} diff --git a/internal/captain/exec/local.go b/internal/captain/exec/local.go new file mode 100644 index 0000000..1e8159c --- /dev/null +++ b/internal/captain/exec/local.go @@ -0,0 +1,39 @@ +// Package exec exposes various task runners that can execute arbitrary commands. This is mostly a thin wrapper around +// `os/exec` plus a mocked implementation. We could extend this in the future to support for remote task runners (abq or +// ssh come to mind). +package exec + +import ( + "context" + "os/exec" + + "github.com/rwx-cloud/cli/internal/captain/errors" +) + +// Local is a local executioner. It wraps `os/exec` +type Local struct{} + +// NewCommand returns a new command that can then be executed. +func (l Local) NewCommand(ctx context.Context, cfg CommandConfig) (Command, error) { + //nolint:gosec // Spawning a user-configurable sub-process is expected here. + cmd := exec.CommandContext(ctx, cfg.Name, cfg.Args...) + + cmd.Stderr = cfg.Stderr + cmd.Stdout = cfg.Stdout + + for _, override := range cfg.Env { + cmd.Env = append(cmd.Environ(), override) + } + + return cmd, nil +} + +// GetExitStatus extracts the exit code from an error +func (l Local) GetExitStatusFromError(err error) (int, error) { + var exitError *exec.ExitError + if errors.As(err, &exitError) { + return exitError.ExitCode(), nil + } + + return 0, errors.NewInternalError("Expected error to be of type exec.ExitError, received %T", err) +} diff --git a/internal/captain/fs/file.go b/internal/captain/fs/file.go new file mode 100644 index 0000000..b4f42a6 --- /dev/null +++ b/internal/captain/fs/file.go @@ -0,0 +1,23 @@ +package fs + +import ( + "io" + "os" +) + +// File is a generic interface that represents a file that was opened on a file-system. It is modelled after the default +// 'os.File' from the standard library. +type File interface { + io.ReadSeekCloser + io.Writer + Name() string + Stat() (os.FileInfo, error) + Sync() error +} + +// TODO: replace with io/fs.File +type ReadOnlyFile interface { + io.ReadSeekCloser + Stat() (os.FileInfo, error) + Name() string +} diff --git a/internal/captain/fs/file_system.go b/internal/captain/fs/file_system.go new file mode 100644 index 0000000..657c721 --- /dev/null +++ b/internal/captain/fs/file_system.go @@ -0,0 +1,22 @@ +package fs + +import "os" + +// FileSystem is an abstraction over file-systems. This is implemented by the default `os` package and can also be used +// for mocking. +type FileSystem interface { + Create(filePath string) (File, error) + CreateTemp(dir string, pattern string) (File, error) + Getwd() (string, error) + Glob(pattern string) ([]string, error) + GlobMany(patterns []string) ([]string, error) + MkdirAll(string, os.FileMode) error + MkdirTemp(string, string) (string, error) + Open(name string) (File, error) + OpenFile(name string, flag int, perm os.FileMode) (File, error) + Remove(name string) error + RemoveAll(path string) error + Rename(oldname string, newname string) error + Stat(name string) (os.FileInfo, error) + TempDir() string +} diff --git a/internal/captain/fs/fs_suite_test.go b/internal/captain/fs/fs_suite_test.go new file mode 100644 index 0000000..f4d3670 --- /dev/null +++ b/internal/captain/fs/fs_suite_test.go @@ -0,0 +1,15 @@ +package fs_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestFs(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "Fs Suite") +} diff --git a/internal/captain/fs/local.go b/internal/captain/fs/local.go new file mode 100644 index 0000000..1df36ff --- /dev/null +++ b/internal/captain/fs/local.go @@ -0,0 +1,129 @@ +// Package fs is a thin wrapper around potential file-systems. By default, it is an abstraction over the `os` package +// from the standard library. +package fs + +import ( + "os" + "path/filepath" + "sort" + "strings" + + doublestar "github.com/bmatcuk/doublestar/v4" + + "github.com/rwx-cloud/cli/internal/captain/errors" +) + +// Local is a local file-system. It wraps the default `os` package +type Local struct{} + +func (l Local) Create(path string) (File, error) { + if err := l.MkdirAll(filepath.Dir(path), 0o750); err != nil { + return nil, errors.WithStack(err) + } + + f, err := os.Create(path) + return f, errors.WithStack(err) +} + +func (l Local) Getwd() (string, error) { + dir, err := os.Getwd() + return dir, errors.WithStack(err) +} + +func (l Local) CreateTemp(dir string, pattern string) (File, error) { + f, err := os.CreateTemp(dir, pattern) + if err != nil { + return nil, errors.WithStack(err) + } + + return f, nil +} + +func (l Local) Glob(pattern string) ([]string, error) { + filepaths, err := doublestar.FilepathGlob(pattern) + if err != nil { + return nil, errors.WithStack(err) + } + + if filepaths == nil { + return []string{}, nil + } + + return filepaths, nil +} + +func (l Local) GlobMany(patterns []string) ([]string, error) { + pathSet := make(map[string]bool) + for _, pattern := range patterns { + isNegation := false + + if strings.HasPrefix(pattern, "!") { + isNegation = true + pattern = strings.TrimPrefix(pattern, "!") + } + + expandedPaths, err := l.Glob(pattern) + if err != nil { + return nil, errors.WithStack(err) + } + for _, filePath := range expandedPaths { + if isNegation { + delete(pathSet, filePath) + } else { + pathSet[filePath] = true + } + } + } + + expandedPaths := make([]string, 0, len(pathSet)) + for expandedPath := range pathSet { + expandedPaths = append(expandedPaths, expandedPath) + } + + sort.Slice(expandedPaths, func(i, j int) bool { + return expandedPaths[i] < expandedPaths[j] + }) + + return expandedPaths, nil +} + +func (l Local) MkdirAll(path string, perm os.FileMode) error { + return errors.WithStack(os.MkdirAll(path, perm)) +} + +func (l Local) MkdirTemp(dir, pattern string) (string, error) { + dir, err := os.MkdirTemp(dir, pattern) + return dir, errors.WithStack(err) +} + +// Open opens a file for reading. Use OpenFile to open a file with different permissions +func (l Local) Open(name string) (File, error) { + f, err := os.Open(name) + return f, errors.WithStack(err) +} + +func (l Local) OpenFile(name string, flag int, perm os.FileMode) (File, error) { + f, err := os.OpenFile(name, flag, perm) + return f, errors.WithStack(err) +} + +func (l Local) Remove(name string) error { + return errors.WithStack(os.Remove(name)) +} + +func (l Local) RemoveAll(path string) error { + return errors.WithStack(os.RemoveAll(path)) +} + +func (l Local) Rename(oldname string, newname string) error { + return errors.WithStack(os.Rename(oldname, newname)) +} + +func (l Local) Stat(name string) (os.FileInfo, error) { + info, err := os.Stat(name) + return info, errors.WithStack(err) +} + +func (l Local) TempDir() string { + return os.TempDir() +} diff --git a/internal/captain/fs/local_test.go b/internal/captain/fs/local_test.go new file mode 100644 index 0000000..8956485 --- /dev/null +++ b/internal/captain/fs/local_test.go @@ -0,0 +1,176 @@ +package fs_test + +import ( + "os" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/fs" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("fs.GlobMany", func() { + It("expands a single glob pattern", func() { + fs := fs.Local{} + expandedPaths, _ := fs.GlobMany([]string{"../../test/fixtures/integration-tests/partition/*_spec.rb"}) + + Expect(expandedPaths).To(Equal([]string{ + "../../test/fixtures/integration-tests/partition/a_spec.rb", + "../../test/fixtures/integration-tests/partition/b_spec.rb", + "../../test/fixtures/integration-tests/partition/c_spec.rb", + "../../test/fixtures/integration-tests/partition/d_spec.rb", + })) + }) + + It("expands multiple glob patterns", func() { + fs := fs.Local{} + expandedPaths, _ := fs.GlobMany([]string{ + "../../test/fixtures/integration-tests/partition/*_spec.rb", + "../../test/fixtures/integration-tests/partition/x.rb", + }) + + Expect(expandedPaths).To(Equal([]string{ + "../../test/fixtures/integration-tests/partition/a_spec.rb", + "../../test/fixtures/integration-tests/partition/b_spec.rb", + "../../test/fixtures/integration-tests/partition/c_spec.rb", + "../../test/fixtures/integration-tests/partition/d_spec.rb", + "../../test/fixtures/integration-tests/partition/x.rb", + })) + }) + + It("supports negation", func() { + fs := fs.Local{} + expandedPaths, _ := fs.GlobMany([]string{ + "../../test/fixtures/integration-tests/partition/*_spec.rb", + "!../../test/fixtures/integration-tests/partition/a_spec.rb", + "!../../test/fixtures/integration-tests/partition/b_spec.rb", + "../../test/fixtures/integration-tests/partition/b_spec.rb", + }) + + Expect(expandedPaths).To(Equal([]string{ + "../../test/fixtures/integration-tests/partition/b_spec.rb", + "../../test/fixtures/integration-tests/partition/c_spec.rb", + "../../test/fixtures/integration-tests/partition/d_spec.rb", + })) + }) + + It("expands multiple glob patterns only returning unique paths", func() { + fs := fs.Local{} + expandedPaths, _ := fs.GlobMany([]string{ + "../../test/fixtures/integration-tests/partition/*_spec.rb", + "../../test/fixtures/integration-tests/partition/*_spec.rb", + }) + + Expect(expandedPaths).To(Equal([]string{ + "../../test/fixtures/integration-tests/partition/a_spec.rb", + "../../test/fixtures/integration-tests/partition/b_spec.rb", + "../../test/fixtures/integration-tests/partition/c_spec.rb", + "../../test/fixtures/integration-tests/partition/d_spec.rb", + })) + }) + + It("sorts the results for determinism", func() { + fs := fs.Local{} + expandedPaths, _ := fs.GlobMany([]string{ + "../../test/fixtures/integration-tests/partition/z.rb", + "../../test/fixtures/integration-tests/partition/y.rb", + "../../test/fixtures/integration-tests/partition/x.rb", + "../../test/fixtures/integration-tests/partition/x.rb", + }) + + Expect(expandedPaths).To(Equal([]string{ + "../../test/fixtures/integration-tests/partition/x.rb", + "../../test/fixtures/integration-tests/partition/y.rb", + "../../test/fixtures/integration-tests/partition/z.rb", + })) + }) + + It("recursively expands double star globs", func() { + fs := fs.Local{} + expandedPaths, _ := fs.GlobMany([]string{ + "../../test/**/*.rb", + }) + + Expect(expandedPaths).To(Equal([]string{ + "../../test/fixtures/integration-tests/partition/a_spec.rb", + "../../test/fixtures/integration-tests/partition/b_spec.rb", + "../../test/fixtures/integration-tests/partition/c_spec.rb", + "../../test/fixtures/integration-tests/partition/d_spec.rb", + "../../test/fixtures/integration-tests/partition/x.rb", + "../../test/fixtures/integration-tests/partition/y.rb", + "../../test/fixtures/integration-tests/partition/z.rb", + })) + }) + + It("supports files with special characters", func() { + fs := fs.Local{} + expandedPaths, err := fs.GlobMany([]string{ + "../../test/fixtures/filenames/**/*.txt", + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(expandedPaths).To(Equal([]string{ + "../../test/fixtures/filenames/nested/$ @=:+{}[]^><~#|.txt", + "../../test/fixtures/filenames/nested/**.txt", + "../../test/fixtures/filenames/nested/*.txt", + "../../test/fixtures/filenames/nested/?.txt", + "../../test/fixtures/filenames/nested/[].txt", + "../../test/fixtures/filenames/nested/\\.txt", + })) + }) + + It("supports escaping *s", func() { + fs := fs.Local{} + expandedPaths, err := fs.GlobMany([]string{ + "../../test/fixtures/filenames/**/\\*.txt", + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(expandedPaths).To(Equal([]string{ + "../../test/fixtures/filenames/nested/*.txt", + })) + }) + + It("supports escaping **s", func() { + fs := fs.Local{} + expandedPaths, err := fs.GlobMany([]string{ + "../../test/fixtures/filenames/**/\\*\\*.txt", + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(expandedPaths).To(Equal([]string{ + "../../test/fixtures/filenames/nested/**.txt", + })) + }) +}) + +var _ = Describe("fs.Stat", func() { + It("returns info on a file that exists", func() { + fs := fs.Local{} + info, err := fs.Stat("../../../go.mod") + + Expect(err).NotTo(HaveOccurred()) + Expect(info).NotTo(BeNil()) + }) + + It("errs on a file that does not exist", func() { + fs := fs.Local{} + info, err := fs.Stat("../../does-not-exist") + + Expect(err).To(HaveOccurred()) + Expect(errors.Is(err, os.ErrNotExist)).To(BeTrue()) + Expect(info).To(BeNil()) + }) +}) + +var _ = Describe("fs.CreateTemp", func() { + It("creates a temporary file", func() { + fs := fs.Local{} + file, err := fs.CreateTemp("", "create-temp-file") + defer os.Remove(file.Name()) + + Expect(err).NotTo(HaveOccurred()) + Expect(file.Name()).To(ContainSubstring("create-temp-file")) + }) +}) diff --git a/internal/captain/fs/utils.go b/internal/captain/fs/utils.go new file mode 100644 index 0000000..bfeecc6 --- /dev/null +++ b/internal/captain/fs/utils.go @@ -0,0 +1,30 @@ +package fs + +import ( + "path/filepath" + "strings" +) + +// IsLocal is a copy of the `unixIsLocal` function introduced in Go 1.20 +// See https://github.com/golang/go/blob/go1.20.1/src/path/filepath/path.go#L190 +func IsLocal(path string) bool { + if filepath.IsAbs(path) || path == "" { + return false + } + hasDots := false + for p := path; p != ""; { + var part string + part, p, _ = strings.Cut(p, "/") + if part == "." || part == ".." { + hasDots = true + break + } + } + if hasDots { + path = filepath.Clean(path) + } + if path == ".." || strings.HasPrefix(path, "../") { + return false + } + return true +} diff --git a/internal/captain/fs/virtual_file.go b/internal/captain/fs/virtual_file.go new file mode 100644 index 0000000..8c472f8 --- /dev/null +++ b/internal/captain/fs/virtual_file.go @@ -0,0 +1,48 @@ +package fs + +import ( + "bytes" + "io/fs" + "os" + "time" +) + +type VirtualReadOnlyFile struct { + *bytes.Reader + FileName string +} + +// Close is needed in order to satisfy the io.Closer interface +func (vf VirtualReadOnlyFile) Close() error { + return nil +} + +// IsDir is needed in order to satisfy the os.FileInfo interface +func (vf VirtualReadOnlyFile) IsDir() bool { + return false +} + +// Mode is needed in order to satisfy the os.FileInfo interface +func (vf VirtualReadOnlyFile) Mode() fs.FileMode { + return fs.ModeIrregular +} + +// ModTime is needed in order to satisfy the os.FileInfo interface +func (vf VirtualReadOnlyFile) ModTime() time.Time { + return time.Now() +} + +// Name is needed in order to satisfy the File interface from above. +func (vf VirtualReadOnlyFile) Name() string { + return vf.FileName +} + +// Stat is needed in order to satisfy the File interface from above. +func (vf VirtualReadOnlyFile) Stat() (os.FileInfo, error) { + return vf, nil +} + +// Sys is needed in order to satisfy the os.FileInfo interface +func (vf VirtualReadOnlyFile) Sys() any { + return nil +} diff --git a/internal/captain/logging/logger.go b/internal/captain/logging/logger.go new file mode 100644 index 0000000..475454a --- /dev/null +++ b/internal/captain/logging/logger.go @@ -0,0 +1,67 @@ +// Package logging is the central logging package of the CLI. It holds our custom log formatters for zap. +package logging + +import ( + "os" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// NewProductionLogger returns a logger that prints Debug, Info, and Warn messages to stdout and the rest to stderr. +func NewProductionLogger() *zap.SugaredLogger { + encoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{ + // These strings are meaningless - they just need to be non-empty for the console encoder. + MessageKey: "M", + LevelKey: "L", + EncodeLevel: func(lvl zapcore.Level, enc zapcore.PrimitiveArrayEncoder) { + // Anything other than "info" logs will have a capitalized level prefix. + if lvl != zapcore.InfoLevel { + zapcore.CapitalColorLevelEncoder(lvl, enc) + } + }, + }) + + infoLevels := zap.LevelEnablerFunc(func(level zapcore.Level) bool { + return level == zapcore.InfoLevel + }) + + errorLevels := zap.LevelEnablerFunc(func(level zapcore.Level) bool { + return !infoLevels(level) && level != zapcore.DebugLevel + }) + + return zap.New(zapcore.NewTee( + zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), infoLevels), + zapcore.NewCore(encoder, zapcore.Lock(os.Stderr), errorLevels), + )).Sugar() +} + +// NewDebugLogger is similar to our production logger, however it also includes debug output & stacktraces +func NewDebugLogger() *zap.SugaredLogger { + encoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{ + // These strings are meaningless - they just need to be non-empty for the console encoder. + LevelKey: "L", + MessageKey: "M", + NameKey: "N", + StacktraceKey: "S", + TimeKey: "T", + EncodeLevel: zapcore.CapitalColorLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + }) + + infoLevels := zap.LevelEnablerFunc(func(level zapcore.Level) bool { + return level == zapcore.InfoLevel + }) + + errorLevels := zap.LevelEnablerFunc(func(level zapcore.Level) bool { + return !infoLevels(level) + }) + + return zap.New(zapcore.NewTee( + zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), infoLevels), + zapcore.NewCore(encoder, zapcore.Lock(os.Stderr), errorLevels), + )).WithOptions( + zap.Development(), + zap.AddStacktrace(zapcore.ErrorLevel), + ).Sugar() +} diff --git a/internal/captain/mint/mint_suite_test.go b/internal/captain/mint/mint_suite_test.go new file mode 100644 index 0000000..541f1c7 --- /dev/null +++ b/internal/captain/mint/mint_suite_test.go @@ -0,0 +1,15 @@ +package mint_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestMint(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "Mint Suite") +} diff --git a/internal/captain/mint/otel_span_attributes.go b/internal/captain/mint/otel_span_attributes.go new file mode 100644 index 0000000..7ea0b4c --- /dev/null +++ b/internal/captain/mint/otel_span_attributes.go @@ -0,0 +1,80 @@ +package mint + +import ( + "encoding/json" + stdErrors "errors" + "os" + "path/filepath" + "sort" + + "go.uber.org/zap" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/fs" +) + +const rwxSuiteIDAttributeKey = "rwx.tests.suite-id" + +func WriteOtelSpanAttributesJSON(fileSystem fs.FileSystem, log *zap.SugaredLogger, attrs map[string]any) error { + otelSpanPath := os.Getenv("RWX_OTEL_SPAN") + if otelSpanPath == "" { + return nil + } + + suiteIDPath := filepath.Join(otelSpanPath, rwxSuiteIDAttributeKey+".json") + if _, hasSuiteID := attrs[rwxSuiteIDAttributeKey]; hasSuiteID { + _, err := fileSystem.Stat(suiteIDPath) + switch { + case err == nil: + if log != nil { + log.Warnf("Skipping RWX OTEL span attributes because they were already written: %s", suiteIDPath) + } + return nil + case !errors.Is(err, os.ErrNotExist): + return errors.Wrapf(err, "unable to stat RWX OTEL span suite-id attribute path %q", suiteIDPath) + } + } + + keys := make([]string, 0, len(attrs)) + for key := range attrs { + keys = append(keys, key) + } + sort.Strings(keys) + + var allErrs []error + + for _, key := range keys { + outputPath := filepath.Join(otelSpanPath, key+".json") + encodedValue, err := json.Marshal(attrs[key]) + if err != nil { + allErrs = append(allErrs, errors.Wrapf(err, "unable to JSON encode RWX OTEL span attribute %q", key)) + continue + } + + file, err := fileSystem.Create(outputPath) + if err != nil { + allErrs = append(allErrs, errors.Wrapf(err, "unable to create RWX OTEL span attribute file %q", outputPath)) + continue + } + + _, writeErr := file.Write(encodedValue) + closeErr := file.Close() + if writeErr != nil { + allErrs = append(allErrs, errors.Wrapf(writeErr, "unable to write RWX OTEL span attribute file %q", outputPath)) + continue + } + if closeErr != nil { + allErrs = append(allErrs, errors.Wrapf(closeErr, "unable to close RWX OTEL span attribute file %q", outputPath)) + continue + } + } + + if len(allErrs) == 0 { + return nil + } + + return errors.Wrap( + stdErrors.Join(allErrs...), + "unable to write one or more RWX OTEL span attributes", + ) +} diff --git a/internal/captain/mint/otel_span_attributes_test.go b/internal/captain/mint/otel_span_attributes_test.go new file mode 100644 index 0000000..4df5f54 --- /dev/null +++ b/internal/captain/mint/otel_span_attributes_test.go @@ -0,0 +1,158 @@ +package mint_test + +import ( + stdErrors "errors" + "os" + "path/filepath" + "strings" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/rwx-cloud/cli/internal/captain/fs" + "github.com/rwx-cloud/cli/internal/captain/mint" + "github.com/rwx-cloud/cli/internal/captain/mocks" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var errFailedToCreateFile = stdErrors.New("failed to create file") + +var _ = Describe("WriteOtelSpanAttributesJSON", func() { + It("is a no-op when RWX_OTEL_SPAN is unset", func() { + Expect(os.Unsetenv("RWX_OTEL_SPAN")).To(Succeed()) + + logger, _ := testLogger() + err := mint.WriteOtelSpanAttributesJSON(&mocks.FileSystem{}, logger, map[string]any{ + "rwx.tests.summary.tests": 1, + }) + + Expect(err).NotTo(HaveOccurred()) + }) + + It("writes typed JSON values", func() { + otelSpanDir, err := os.MkdirTemp("", "otel-span-attrs-*") + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { + Expect(os.RemoveAll(otelSpanDir)).To(Succeed()) + }) + + Expect(os.Setenv("RWX_OTEL_SPAN", otelSpanDir)).To(Succeed()) + DeferCleanup(func() { + Expect(os.Unsetenv("RWX_OTEL_SPAN")).To(Succeed()) + }) + + logger, _ := testLogger() + err = mint.WriteOtelSpanAttributesJSON(fs.Local{}, logger, map[string]any{ + "rwx.tests.suite-id": "suite-1", + "rwx.tests.summary.tests": 12, + "rwx.tests.summary.flaky": 2, + "rwx.tests.summary.status": "failed", + "rwx.tests.retry.enabled": true, + "rwx.tests.retry.note": nil, + }) + + Expect(err).NotTo(HaveOccurred()) + Expect(readFile(filepath.Join(otelSpanDir, "rwx.tests.suite-id.json"))).To(Equal(`"suite-1"`)) + Expect(readFile(filepath.Join(otelSpanDir, "rwx.tests.summary.tests.json"))).To(Equal(`12`)) + Expect(readFile(filepath.Join(otelSpanDir, "rwx.tests.summary.flaky.json"))).To(Equal(`2`)) + Expect(readFile(filepath.Join(otelSpanDir, "rwx.tests.summary.status.json"))).To(Equal(`"failed"`)) + Expect(readFile(filepath.Join(otelSpanDir, "rwx.tests.retry.enabled.json"))).To(Equal(`true`)) + Expect(readFile(filepath.Join(otelSpanDir, "rwx.tests.retry.note.json"))).To(Equal(`null`)) + }) + + It("skips all attributes when the suite-id attribute already exists", func() { + otelSpanDir, err := os.MkdirTemp("", "otel-span-attrs-*") + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { + Expect(os.RemoveAll(otelSpanDir)).To(Succeed()) + }) + + Expect(os.Setenv("RWX_OTEL_SPAN", otelSpanDir)).To(Succeed()) + DeferCleanup(func() { + Expect(os.Unsetenv("RWX_OTEL_SPAN")).To(Succeed()) + }) + + existingPath := filepath.Join(otelSpanDir, "rwx.tests.suite-id.json") + Expect(os.WriteFile(existingPath, []byte(`99`), 0o600)).To(Succeed()) + + logger, recordedLogs := testLogger() + err = mint.WriteOtelSpanAttributesJSON(fs.Local{}, logger, map[string]any{ + "rwx.tests.suite-id": "suite-1", + "rwx.tests.summary.tests": 7, + "rwx.tests.summary.flaky": 1, + }) + + Expect(err).NotTo(HaveOccurred()) + Expect(readFile(existingPath)).To(Equal(`99`)) + _, err = os.Stat(filepath.Join(otelSpanDir, "rwx.tests.summary.tests.json")) + Expect(os.IsNotExist(err)).To(BeTrue()) + _, err = os.Stat(filepath.Join(otelSpanDir, "rwx.tests.summary.flaky.json")) + Expect(os.IsNotExist(err)).To(BeTrue()) + + sawWarning := false + for _, entry := range recordedLogs.All() { + if strings.Contains(entry.Message, "already written") && strings.Contains(entry.Message, existingPath) { + sawWarning = true + break + } + } + + Expect(sawWarning).To(BeTrue()) + }) + + It("returns an error when creating an attribute file fails", func() { + Expect(os.Setenv("RWX_OTEL_SPAN", "/tmp/rwx-otel-span")).To(Succeed()) + DeferCleanup(func() { + Expect(os.Unsetenv("RWX_OTEL_SPAN")).To(Succeed()) + }) + + logger, _ := testLogger() + err := mint.WriteOtelSpanAttributesJSON(&mocks.FileSystem{ + MockStat: func(string) (os.FileInfo, error) { + return nil, os.ErrNotExist + }, + MockCreate: func(string) (fs.File, error) { + return nil, errFailedToCreateFile + }, + }, logger, map[string]any{ + "rwx.tests.summary.tests": 2, + }) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("unable to write one or more RWX OTEL span attributes")) + Expect(err.Error()).To(ContainSubstring("unable to create RWX OTEL span attribute file")) + }) + + It("returns an error when marshalling fails", func() { + Expect(os.Setenv("RWX_OTEL_SPAN", "/tmp/rwx-otel-span")).To(Succeed()) + DeferCleanup(func() { + Expect(os.Unsetenv("RWX_OTEL_SPAN")).To(Succeed()) + }) + + logger, _ := testLogger() + err := mint.WriteOtelSpanAttributesJSON(&mocks.FileSystem{ + MockStat: func(string) (os.FileInfo, error) { + return nil, os.ErrNotExist + }, + }, logger, map[string]any{ + "rwx.tests.summary.tests": func() {}, + }) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("unable to JSON encode RWX OTEL span attribute")) + }) +}) + +func readFile(path string) string { + data, err := os.ReadFile(path) + Expect(err).NotTo(HaveOccurred()) + return string(data) +} + +func testLogger() (*zap.SugaredLogger, *observer.ObservedLogs) { + core, recordedLogs := observer.New(zapcore.InfoLevel) + return zap.New(core).Sugar(), recordedLogs +} diff --git a/internal/captain/mint/retry_actions.go b/internal/captain/mint/retry_actions.go new file mode 100644 index 0000000..0b04a47 --- /dev/null +++ b/internal/captain/mint/retry_actions.go @@ -0,0 +1,315 @@ +package mint + +import ( + "encoding/json" + "io" + "os" + "path/filepath" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/fs" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +const ( + retryActionKey = "retry-failed-tests" + retryActionLabel = "Retry failed tests" + retryActionDescription = "Only the tests that failed will be run again." + retryActionEnv = "RWX_TEST_MINT_RETRY_FAILED_TESTS" +) + +func IsMint() bool { + return os.Getenv("MINT") == "true" +} + +func DidRetryFailedTests() bool { + return os.Getenv(retryActionEnv) == "true" || os.Getenv("CAPTAIN_MINT_RETRY_FAILED_TESTS") == "true" +} + +func WriteConfigureRetryCommandTip(fs fs.FileSystem) error { + var err error + + tipFile, err := fs.Create(filepath.Join(os.Getenv("MINT_TIPS"), "configure-captain-retry-command")) + if err != nil { + return errors.Wrap(err, "unable to create tip file") + } + defer tipFile.Close() + + _, err = tipFile.Write([]byte("Configure Captain retry command to enable Mint to retry only failed tests.")) + if err != nil { + return errors.Wrap(err, "unable to write to tip file") + } + + return nil +} + +func WriteRetryFailedTestsAction(fs fs.FileSystem, testResults v1.TestResults, intermediateArtifactsPath string) error { + var err error + + err = writeEnv(fs) + if err != nil { + return errors.Wrap(err, "unable to write the Mint retry action env file") + } + + err = writeLabel(fs) + if err != nil { + return errors.Wrap(err, "unable to write the Mint retry action label file") + } + + err = writeDescription(fs) + if err != nil { + return errors.Wrap(err, "unable to write the Mint retry action description file") + } + + err = writeTestResults(fs, testResults) + if err != nil { + return errors.Wrap(err, "unable to write the Mint retry action test results file") + } + + err = writeIntermediateArtifacts(fs, intermediateArtifactsPath) + if err != nil { + return errors.Wrap(err, "unable to write the Mint retry action intermediate artifacts") + } + + return nil +} + +func writeIntermediateArtifacts(fs fs.FileSystem, intermediateArtifactsPath string) error { + if intermediateArtifactsPath == "" { + return nil + } + + info, err := fs.Stat(intermediateArtifactsPath) + if os.IsNotExist(err) { + return nil + } + if err != nil { + return errors.Wrap(err, "unable to check intermediate artifacts directory") + } + if !info.IsDir() { + return nil + } + + srcPath := intermediateArtifactsPath + dstPath := filepath.Join(retryActionDirectory(), "data", "intermediate-artifacts") + + err = copyDirectory(fs, srcPath, dstPath) + if err != nil { + return errors.Wrap(err, "unable to copy intermediate artifacts to Mint retry action directory") + } + + return nil +} + +func copyDirectory(fs fs.FileSystem, src, dst string) error { + err := fs.MkdirAll(dst, 0o750) + if err != nil { + return errors.WithStack(err) + } + + pattern := filepath.Join(src, "**", "*") + allPaths, err := fs.Glob(pattern) + if err != nil { + return errors.WithStack(err) + } + + for _, srcPath := range allPaths { + relPath, err := filepath.Rel(src, srcPath) + if err != nil { + return errors.WithStack(err) + } + dstPath := filepath.Join(dst, relPath) + + info, err := fs.Stat(srcPath) + if err != nil { + return errors.WithStack(err) + } + + if info.IsDir() { + err = fs.MkdirAll(dstPath, 0o750) + if err != nil { + return errors.WithStack(err) + } + } else { + parentDir := filepath.Dir(dstPath) + err = fs.MkdirAll(parentDir, 0o750) + if err != nil { + return errors.WithStack(err) + } + + err = copyFile(fs, srcPath, dstPath) + if err != nil { + return errors.WithStack(err) + } + } + } + + return nil +} + +func copyFile(fs fs.FileSystem, src, dst string) error { + srcFile, err := fs.Open(src) + if err != nil { + return errors.WithStack(err) + } + defer srcFile.Close() + + dstFile, err := fs.Create(dst) + if err != nil { + return errors.WithStack(err) + } + defer dstFile.Close() + + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return errors.WithStack(err) + } + + return errors.WithStack(dstFile.Sync()) +} + +func ReadFailedTestResults(fs fs.FileSystem) (*v1.TestResults, error) { + var err error + + testResultsFile, err := fs.Open(filepath.Join(retryDataDirectory(), "test-results.json")) + if err != nil { + return nil, errors.Wrap(err, "unable to open test results file") + } + defer testResultsFile.Close() + + var testResults v1.TestResults + err = json.NewDecoder(testResultsFile).Decode(&testResults) + if err != nil { + return nil, errors.Wrap(err, "unable to decode test results from JSON") + } + + return &testResults, nil +} + +func RestoreIntermediateArtifacts(fs fs.FileSystem, intermediateArtifactsPath string) error { + if intermediateArtifactsPath == "" { + return nil + } + + srcPath := filepath.Join(retryDataDirectory(), "intermediate-artifacts") + info, err := fs.Stat(srcPath) + if os.IsNotExist(err) { + return nil + } + if err != nil { + return errors.Wrap(err, "unable to check mint retry data artifacts directory") + } + if !info.IsDir() { + return nil + } + + err = copyDirectory(fs, srcPath, intermediateArtifactsPath) + if err != nil { + return errors.Wrap(err, "unable to restore intermediate artifacts from mint retry data") + } + + return nil +} + +func WriteError(fs fs.FileSystem, errorMessage string) error { + var err error + + errorFile, err := fs.CreateTemp(errorsDirectory(), "captain-") + if err != nil { + return errors.Wrap(err, "unable to create error file") + } + defer errorFile.Close() + + _, err = errorFile.Write([]byte(errorMessage)) + if err != nil { + return errors.Wrap(err, "unable to write to error file") + } + + return nil +} + +func RetryFailedTestsLabel() string { + return retryActionLabel +} + +func errorsDirectory() string { + return os.Getenv("MINT_ERRORS") +} + +func retryDataDirectory() string { + return os.Getenv("MINT_RETRY_DATA") +} + +func retryActionDirectory() string { + return filepath.Join(os.Getenv("MINT_RETRY_ACTIONS"), retryActionKey) +} + +func writeEnv(fs fs.FileSystem) error { + var err error + + envFile, err := fs.Create(filepath.Join(retryActionDirectory(), "env", retryActionEnv)) + if err != nil { + return errors.Wrap(err, "unable to create env file") + } + defer envFile.Close() + + _, err = envFile.Write([]byte("true")) + if err != nil { + return errors.Wrap(err, "unable to write to env file") + } + + return nil +} + +func writeLabel(fs fs.FileSystem) error { + var err error + + labelFile, err := fs.Create(filepath.Join(retryActionDirectory(), "label")) + if err != nil { + return errors.Wrap(err, "unable to create label file") + } + defer labelFile.Close() + + _, err = labelFile.Write([]byte(RetryFailedTestsLabel())) + if err != nil { + return errors.Wrap(err, "unable to write to label file") + } + + return nil +} + +func writeDescription(fs fs.FileSystem) error { + var err error + + descriptionFile, err := fs.Create(filepath.Join(retryActionDirectory(), "description")) + if err != nil { + return errors.Wrap(err, "unable to create description file") + } + defer descriptionFile.Close() + + _, err = descriptionFile.Write([]byte(retryActionDescription)) + if err != nil { + return errors.Wrap(err, "unable to write to description file") + } + + return nil +} + +func writeTestResults(fs fs.FileSystem, testResults v1.TestResults) error { + var err error + + testResultsFile, err := fs.Create(filepath.Join(retryActionDirectory(), "data", "test-results.json")) + if err != nil { + return errors.Wrap(err, "unable to create test results file") + } + defer testResultsFile.Close() + + encoder := json.NewEncoder(testResultsFile) + encoder.SetIndent("", "") // make them compact + + if err := encoder.Encode(testResults); err != nil { + return errors.Wrap(err, "unable to encode test results to JSON") + } + + return nil +} diff --git a/internal/captain/mocks/backend.go b/internal/captain/mocks/backend.go new file mode 100644 index 0000000..c8dfb36 --- /dev/null +++ b/internal/captain/mocks/backend.go @@ -0,0 +1,69 @@ +package mocks + +import ( + "context" + + "github.com/rwx-cloud/cli/internal/captain/backend" + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/testing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +// API is a mocked implementation of 'backend.Client'. +type API struct { + MockGetRunConfiguration func(context.Context, string) (backend.RunConfiguration, error) + MockGetQuarantinedTests func(context.Context, string) ([]backend.Test, error) + MockGetTestTimingManifest func(context.Context, string) ([]testing.TestFileTiming, error) + MockUpdateTestResults func(context.Context, string, v1.TestResults) ( + []backend.TestResultsUploadResult, error, + ) +} + +// GetRunConfiguration either calls the configured mock of itself or returns an error if that doesn't exist. +func (a *API) GetRunConfiguration( + ctx context.Context, + testSuiteIdentifier string, +) (backend.RunConfiguration, error) { + if a.MockGetRunConfiguration != nil { + return a.MockGetRunConfiguration(ctx, testSuiteIdentifier) + } + + return backend.RunConfiguration{}, errors.NewInternalError("MockGetRunConfiguration was not configured") +} + +// GetQuarantinedTests either calls the configured mock of itself or returns an error if that doesn't exist. +func (a *API) GetQuarantinedTests( + ctx context.Context, + testSuiteIdentifier string, +) ([]backend.Test, error) { + if a.MockGetQuarantinedTests != nil { + return a.MockGetQuarantinedTests(ctx, testSuiteIdentifier) + } + + return nil, errors.NewInternalError("MockGetQuarantinedTests was not configured") +} + +// GetTestTimingManifest either calls the configured mock of itself or returns an error if that doesn't exist. +func (a *API) GetTestTimingManifest( + ctx context.Context, + testSuiteIdentifier string, +) ([]testing.TestFileTiming, error) { + if a.MockGetTestTimingManifest != nil { + return a.MockGetTestTimingManifest(ctx, testSuiteIdentifier) + } + + return nil, errors.NewInternalError("MockGetTestTimingManifest was not configured") +} + +// UploadTestResults either calls the configured mock of itself or returns an error if that doesn't exist. +func (a *API) UpdateTestResults( + ctx context.Context, + testSuiteID string, + testResults v1.TestResults, +) ([]backend.TestResultsUploadResult, error) { + if a.MockUpdateTestResults != nil { + return a.MockUpdateTestResults(ctx, testSuiteID, testResults) + } + + return nil, errors.NewInternalError("MockUpdateTestResults was not configured") +} diff --git a/internal/captain/mocks/command.go b/internal/captain/mocks/command.go new file mode 100644 index 0000000..ee41eba --- /dev/null +++ b/internal/captain/mocks/command.go @@ -0,0 +1,27 @@ +package mocks + +import "github.com/rwx-cloud/cli/internal/captain/errors" + +// API is a mocked implementation of 'exec.Command'. +type Command struct { + MockStart func() error + MockWait func() error +} + +// Start either calls the configured mock of itself or returns an error if that doesn't exist. +func (c *Command) Start() error { + if c.MockStart != nil { + return c.MockStart() + } + + return errors.NewInternalError("MockStart was not configured") +} + +// Wait either calls the configured mock of itself or returns an error if that doesn't exist. +func (c *Command) Wait() error { + if c.MockWait != nil { + return c.MockWait() + } + + return errors.NewInternalError("MockWait was not configured") +} diff --git a/internal/captain/mocks/file.go b/internal/captain/mocks/file.go new file mode 100644 index 0000000..ff703d1 --- /dev/null +++ b/internal/captain/mocks/file.go @@ -0,0 +1,70 @@ +package mocks + +import ( + "io/fs" + "os" + "strings" + "time" +) + +// File is a mocked implementation of `os.File`, based on a common `bytes.Reader` +type File struct { + *strings.Builder + *strings.Reader + + MockModTime func() time.Time + MockMode func() fs.FileMode + MockName func() string +} + +// Close will always return nil. +func (f *File) Close() error { + return nil +} + +// Mode either calls the configured mock of itself or returns `fs.ModeIrregular` +func (f *File) Mode() fs.FileMode { + if f.MockMode != nil { + return f.MockMode() + } + + return fs.ModeIrregular +} + +// IsDir will always return false. +func (f *File) IsDir() bool { + return false +} + +// ModTime either calls the configured mock of itself or returns `time.Now` +func (f *File) ModTime() time.Time { + if f.MockModTime != nil { + return f.MockModTime() + } + + return time.Now() +} + +// Name either calls the configured mock of itself or returns an empty string +func (f *File) Name() string { + if f.MockName != nil { + return f.MockName() + } + + return "" +} + +// Stat is a no-op. This mocked file implementation covers the `os.FileInfo` interface already. +func (f *File) Stat() (os.FileInfo, error) { + return f, nil +} + +// Sync always returns nil. +func (f *File) Sync() error { + return nil +} + +// Sys always returns nil. +func (f *File) Sys() any { + return nil +} diff --git a/internal/captain/mocks/file_info.go b/internal/captain/mocks/file_info.go new file mode 100644 index 0000000..34987fe --- /dev/null +++ b/internal/captain/mocks/file_info.go @@ -0,0 +1,43 @@ +package mocks + +import ( + "io/fs" + "time" +) + +// FileInfo is a mocked implementation of `fs.FileInfo` +type FileInfo struct { + Dir bool + FileName string + FileMode fs.FileMode + FileSize int64 + ModifiedAt time.Time + + MockModTime func() time.Time + MockMode func() fs.FileMode +} + +func (f FileInfo) Mode() fs.FileMode { + return f.FileMode +} + +func (f FileInfo) IsDir() bool { + return f.Dir +} + +func (f FileInfo) ModTime() time.Time { + return f.ModifiedAt +} + +func (f FileInfo) Name() string { + return f.FileName +} + +func (f FileInfo) Size() int64 { + return f.FileSize +} + +// Sys always returns nil. +func (f FileInfo) Sys() any { + return nil +} diff --git a/internal/captain/mocks/file_system.go b/internal/captain/mocks/file_system.go new file mode 100644 index 0000000..15c276b --- /dev/null +++ b/internal/captain/mocks/file_system.go @@ -0,0 +1,144 @@ +package mocks + +import ( + "os" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/fs" +) + +// FileSystem is a mocked implementation of 'cli.FileSystem'. +type FileSystem struct { + MockCreate func(filePath string) (fs.File, error) + MockCreateTemp func(dir string, pattern string) (fs.File, error) + MockGetwd func() (string, error) + MockGlob func(pattern string) ([]string, error) + MockGlobMany func(patterns []string) ([]string, error) + MockMkdirAll func(string, os.FileMode) error + MockMkdirTemp func(string, string) (string, error) + MockOpen func(name string) (fs.File, error) + MockOpenFile func(name string, flag int, perm os.FileMode) (fs.File, error) + MockRemove func(name string) error + MockRemoveAll func(path string) error + MockRename func(oldname string, newname string) error + MockStat func(name string) (os.FileInfo, error) + MockTempDir func() string +} + +// Create either calls the configured mock of itself or returns an error if that doesn't exist. +func (f *FileSystem) Create(filePath string) (fs.File, error) { + if f.MockCreate != nil { + return f.MockCreate(filePath) + } + + return nil, errors.NewInternalError("MockCreate was not configured") +} + +func (f *FileSystem) Getwd() (string, error) { + if f.MockGetwd != nil { + return f.MockGetwd() + } + + return "", errors.NewInternalError("MockGetwd was not configured") +} + +// CreateTemp either calls the configured mock of itself or returns an error if that doesn't exist. +func (f *FileSystem) CreateTemp(dir string, pattern string) (fs.File, error) { + if f.MockCreateTemp != nil { + return f.MockCreateTemp(dir, pattern) + } + + return nil, errors.NewInternalError("MockCreateTemp was not configured") +} + +func (f *FileSystem) Glob(pattern string) ([]string, error) { + if f.MockGlob != nil { + return f.MockGlob(pattern) + } + + return nil, errors.NewInternalError("MockGlob was not configured") +} + +func (f *FileSystem) GlobMany(patterns []string) ([]string, error) { + if f.MockGlobMany != nil { + return f.MockGlobMany(patterns) + } + + if f.MockGlob != nil { + return f.MockGlob(patterns[0]) + } + + return nil, errors.NewInternalError("MockGlob was not configured") +} + +func (f *FileSystem) Open(name string) (fs.File, error) { + if f.MockOpen != nil { + return f.MockOpen(name) + } + + return nil, errors.NewInternalError("MockOpen was not configured") +} + +func (f *FileSystem) OpenFile(name string, flag int, perm os.FileMode) (fs.File, error) { + if f.MockOpenFile != nil { + return f.MockOpenFile(name, flag, perm) + } + + return nil, errors.NewInternalError("MockOpenFile was not configured") +} + +func (f *FileSystem) MkdirAll(path string, perm os.FileMode) error { + if f.MockMkdirAll != nil { + return f.MockMkdirAll(path, perm) + } + + return errors.NewInternalError("MockMkdirAll was not configured") +} + +func (f *FileSystem) MkdirTemp(dir, pattern string) (string, error) { + if f.MockMkdirTemp != nil { + return f.MockMkdirTemp(dir, pattern) + } + + return "", errors.NewInternalError("MockMkdirTemp was not configured") +} + +func (f *FileSystem) Remove(name string) error { + if f.MockRemove != nil { + return f.MockRemove(name) + } + + return errors.NewInternalError("MockRemove was not configured") +} + +func (f *FileSystem) RemoveAll(path string) error { + if f.MockRemoveAll != nil { + return f.MockRemoveAll(path) + } + + return errors.NewInternalError("MockRemoveAll was not configured") +} + +func (f *FileSystem) Rename(oldname string, newname string) error { + if f.MockRename != nil { + return f.MockRename(oldname, newname) + } + + return errors.NewInternalError("MockRename was not configured") +} + +func (f *FileSystem) Stat(name string) (os.FileInfo, error) { + if f.MockStat != nil { + return f.MockStat(name) + } + + return nil, errors.NewInternalError("MockStat was not configured") +} + +func (f *FileSystem) TempDir() string { + if f.MockTempDir != nil { + return f.MockTempDir() + } + + return "tmp" +} diff --git a/internal/captain/mocks/parser.go b/internal/captain/mocks/parser.go new file mode 100644 index 0000000..559e0c5 --- /dev/null +++ b/internal/captain/mocks/parser.go @@ -0,0 +1,22 @@ +package mocks + +import ( + "io" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +// Parser is a mocked implementation of 'parsing.Parser'. +type Parser struct { + MockParse func(io.Reader) (*v1.TestResults, error) +} + +// Parse either calls the configured mock of itself or returns an error if that doesn't exist. +func (p *Parser) Parse(reader io.Reader) (*v1.TestResults, error) { + if p.MockParse != nil { + return p.MockParse(reader) + } + + return nil, errors.NewInternalError("MockParser was not configured") +} diff --git a/internal/captain/mocks/task_runner.go b/internal/captain/mocks/task_runner.go new file mode 100644 index 0000000..20c46ef --- /dev/null +++ b/internal/captain/mocks/task_runner.go @@ -0,0 +1,32 @@ +package mocks + +import ( + "context" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/exec" +) + +// TaskRunner is a mocked implementation of 'cli.TaskRunner'. +type TaskRunner struct { + MockNewCommand func(ctx context.Context, cfg exec.CommandConfig) (exec.Command, error) + MockGetExitStatusFromError func(error) (int, error) +} + +// NewCommand either calls the configured mock of itself or returns an error if that doesn't exist. +func (t *TaskRunner) NewCommand(ctx context.Context, cfg exec.CommandConfig) (exec.Command, error) { + if t.MockNewCommand != nil { + return t.MockNewCommand(ctx, cfg) + } + + return nil, errors.NewInternalError("MockNewCommand was not configured") +} + +// GetExitStatusFromError either calls the configured mock of itself or returns an error if that doesn't exist. +func (t *TaskRunner) GetExitStatusFromError(err error) (int, error) { + if t.MockGetExitStatusFromError != nil { + return t.MockGetExitStatusFromError(err) + } + + return 0, errors.NewInternalError("MockGetExitStatusFromError was not configured") +} diff --git a/internal/captain/parsing/.snapshots/DotNetxUnitParser Parse parses the sample file b/internal/captain/parsing/.snapshots/DotNetxUnitParser Parse parses the sample file new file mode 100644 index 0000000..bd948f5 --- /dev/null +++ b/internal/captain/parsing/.snapshots/DotNetxUnitParser Parse parses the sample file @@ -0,0 +1,284 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": ".NET", + "kind": "xUnit" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 15, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 1, + "pended": 0, + "quarantined": 0, + "skipped": 1, + "successful": 13, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "NullAssertsTests+Null.Success", + "scope": "test.xunit.assert.dll", + "location": { + "file": "some/path/to/NullAssertsTests.cs", + "line": 15 + }, + "attempt": { + "durationInNanoseconds": 6370900, + "meta": { + "assembly": "test.xunit.assert.dll", + "method": "Success", + "trait-Assembly": "Trait", + "type": "NullAssertsTests+Null" + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "NullAssertsTests+Null.Failure", + "scope": "test.xunit.assert.dll", + "attempt": { + "durationInNanoseconds": 11433500, + "meta": { + "assembly": "test.xunit.assert.dll", + "method": "Failure", + "trait-Assembly": "Trait", + "type": "NullAssertsTests+Null" + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "RangeAssertsTests+InRange.DoubleNotWithinRange", + "scope": "test.xunit.assert.dll", + "attempt": { + "durationInNanoseconds": 2485800, + "meta": { + "assembly": "test.xunit.assert.dll", + "method": "DoubleNotWithinRange", + "trait-Assembly": "Trait", + "type": "RangeAssertsTests+InRange" + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "RangeAssertsTests+InRange.IntValueWithinRange", + "scope": "test.xunit.assert.dll", + "attempt": { + "durationInNanoseconds": 559500, + "meta": { + "assembly": "test.xunit.assert.dll", + "method": "IntValueWithinRange", + "trait-Assembly": "Trait", + "type": "RangeAssertsTests+InRange" + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "RangeAssertsTests+InRange.DoubleValueWithinRange", + "scope": "test.xunit.assert.dll", + "attempt": { + "durationInNanoseconds": 104400, + "meta": { + "assembly": "test.xunit.assert.dll", + "method": "DoubleValueWithinRange", + "trait-Assembly": "Trait", + "type": "RangeAssertsTests+InRange" + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "RangeAssertsTests+InRange.IntNotWithinRangeWithZeroMinimum", + "scope": "test.xunit.assert.dll", + "location": { + "file": "some/path/to/RangeAssertsTests.cs", + "line": 42 + }, + "attempt": { + "durationInNanoseconds": 307700, + "meta": { + "assembly": "test.xunit.assert.dll", + "method": "IntNotWithinRangeWithZeroMinimum", + "trait-Assembly": "Trait", + "type": "RangeAssertsTests+InRange" + }, + "status": { + "kind": "skipped", + "message": "for some reason" + } + } + }, + { + "name": "RangeAssertsTests+InRange.IntNotWithinRangeWithZeroActual", + "scope": "test.xunit.assert.dll", + "attempt": { + "durationInNanoseconds": 299900, + "meta": { + "assembly": "test.xunit.assert.dll", + "method": "IntNotWithinRangeWithZeroActual", + "trait-Assembly": "Trait", + "type": "RangeAssertsTests+InRange" + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "RangeAssertsTests+InRange.StringNotWithinRange", + "scope": "test.xunit.assert.dll", + "attempt": { + "durationInNanoseconds": 1059900, + "meta": { + "assembly": "test.xunit.assert.dll", + "method": "StringNotWithinRange", + "trait-Assembly": "Trait", + "type": "RangeAssertsTests+InRange" + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "RangeAssertsTests+InRange.StringValueWithinRange", + "scope": "test.xunit.assert.dll", + "attempt": { + "durationInNanoseconds": 129800, + "meta": { + "assembly": "test.xunit.assert.dll", + "method": "StringValueWithinRange", + "trait-Assembly": "Trait", + "type": "RangeAssertsTests+InRange" + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "CommandLineTests+MethodArgument.MultipleValidMethodArguments", + "scope": "test.xunit.console.dll", + "attempt": { + "durationInNanoseconds": 11454300, + "meta": { + "assembly": "test.xunit.console.dll", + "method": "MultipleValidMethodArguments", + "trait-Assembly": "Trait", + "type": "CommandLineTests+MethodArgument" + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "CommandLineTests+MethodArgument.MethodArgumentNotPassed", + "scope": "test.xunit.console.dll", + "location": { + "file": "some/path/to/FileName.cs", + "line": 30 + }, + "attempt": { + "durationInNanoseconds": 323400, + "meta": { + "assembly": "test.xunit.console.dll", + "method": "MethodArgumentNotPassed", + "trait-Assembly": "Trait", + "type": "CommandLineTests+MethodArgument" + }, + "status": { + "kind": "failed", + "message": "Assert.Equal() Failure\\r\\nExpected: 10\\r\\nActual: 3", + "exception": "Xunit.Sdk.EqualException", + "backtrace": [ + "at xunit.console.CommandLineTests.MethodArgument.MethodArgumentNotPassed() in some/path/to/FileName.cs:line 30" + ] + } + } + }, + { + "name": "CommandLineTests+MethodArgument.SingleValidMethodArgument", + "scope": "test.xunit.console.dll", + "attempt": { + "durationInNanoseconds": 425700, + "meta": { + "assembly": "test.xunit.console.dll", + "method": "SingleValidMethodArgument", + "trait-Assembly": "Trait", + "type": "CommandLineTests+MethodArgument" + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "CommandLineTests+MethodArgument.MissingOptionValue", + "scope": "test.xunit.console.dll", + "attempt": { + "durationInNanoseconds": 2254700, + "meta": { + "assembly": "test.xunit.console.dll", + "method": "MissingOptionValue", + "trait-Assembly": "Trait", + "type": "CommandLineTests+MethodArgument" + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "CommandLineTests+SerializeOption.SerializeSetSerializeIsTrue", + "scope": "test.xunit.console.dll", + "attempt": { + "durationInNanoseconds": 288600, + "meta": { + "assembly": "test.xunit.console.dll", + "method": "SerializeSetSerializeIsTrue", + "trait-Assembly": "Trait", + "type": "CommandLineTests+SerializeOption" + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "CommandLineTests+SerializeOption.SerializeNotSetSerializeIsFalse", + "scope": "test.xunit.console.dll", + "attempt": { + "durationInNanoseconds": 403900, + "meta": { + "assembly": "test.xunit.console.dll", + "method": "SerializeNotSetSerializeIsFalse", + "trait-Assembly": "Trait", + "type": "CommandLineTests+SerializeOption" + }, + "status": { + "kind": "successful" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/ElixirExUnitParser Parse parses the sample file b/internal/captain/parsing/.snapshots/ElixirExUnitParser Parse parses the sample file new file mode 100644 index 0000000..9fac9fe --- /dev/null +++ b/internal/captain/parsing/.snapshots/ElixirExUnitParser Parse parses the sample file @@ -0,0 +1,126 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Elixir", + "kind": "ExUnit" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 7, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 2, + "pended": 0, + "quarantined": 0, + "skipped": 1, + "successful": 4, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "Elixir.ExunitexampleWeb.ErrorViewTest test renders 404.html", + "location": { + "file": "test/exunitexample_web/views/error_view_test.exs", + "line": 7 + }, + "attempt": { + "durationInNanoseconds": 200000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "Elixir.ExunitexampleWeb.ErrorViewTest test renders 500.html", + "location": { + "file": "test/exunitexample_web/views/error_view_test.exs", + "line": 11 + }, + "attempt": { + "durationInNanoseconds": 21600000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "Elixir.ExunitexampleWeb.ExceptionTest test throws an exception", + "location": { + "file": "test/exunitexample_web/views/exception_test.exs", + "line": 7 + }, + "attempt": { + "durationInNanoseconds": 8100000, + "status": { + "kind": "failed", + "message": "throw: \"some exception\"", + "backtrace": [ + "test/exunitexample_web/views/exception_test.exs:8: (test)" + ] + } + } + }, + { + "name": "Elixir.ExunitexampleWeb.FailingTest test is failing", + "location": { + "file": "test/exunitexample_web/views/failing_test.exs", + "line": 7 + }, + "attempt": { + "durationInNanoseconds": 8100000, + "status": { + "kind": "failed", + "message": "Expected truthy, got false", + "backtrace": [ + "test/exunitexample_web/views/failing_test.exs:8: (test)" + ] + } + } + }, + { + "name": "Elixir.ExunitexampleWeb.PageControllerTest test GET /", + "location": { + "file": "test/exunitexample_web/controllers/page_controller_test.exs", + "line": 4 + }, + "attempt": { + "durationInNanoseconds": 45600000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "Elixir.ExunitexampleWeb.SkippedTest test is pending", + "location": { + "file": "test/exunitexample_web/views/skipped_test.exs", + "line": 8 + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "skipped", + "message": "due to pending filter" + } + } + }, + { + "name": "Elixir.ExunitexampleWeb.SlowTest test is slow", + "location": { + "file": "test/exunitexample_web/views/slow_test.exs", + "line": 7 + }, + "attempt": { + "durationInNanoseconds": 1508400000, + "status": { + "kind": "successful" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/GoGinkgoParser Parse parses other errors b/internal/captain/parsing/.snapshots/GoGinkgoParser Parse parses other errors new file mode 100644 index 0000000..0f22589 --- /dev/null +++ b/internal/captain/parsing/.snapshots/GoGinkgoParser Parse parses other errors @@ -0,0 +1,58 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Go", + "kind": "Ginkgo" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 1, + "flaky": 0, + "otherErrors": 1, + "retries": 0, + "canceled": 0, + "failed": 0, + "pended": 1, + "quarantined": 0, + "skipped": 0, + "successful": 0, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "Foo passes outside a context", + "lineage": [ + "Foo", + "passes outside a context" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "line": 14 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "labels": [] + }, + "status": { + "kind": "pended" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T14:22:04.752611-05:00", + "finishedAt": "0001-01-01T00:00:00Z" + } + } + ], + "otherErrors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1" + }, + "message": "Detected pending specs and --fail-on-pending is set" + } + ] +} diff --git a/internal/captain/parsing/.snapshots/GoGinkgoParser Parse parses the sample file b/internal/captain/parsing/.snapshots/GoGinkgoParser Parse parses the sample file new file mode 100644 index 0000000..bcc0bc4 --- /dev/null +++ b/internal/captain/parsing/.snapshots/GoGinkgoParser Parse parses the sample file @@ -0,0 +1,1013 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Go", + "kind": "Ginkgo" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 31, + "flaky": 1, + "otherErrors": 0, + "retries": 13, + "canceled": 0, + "failed": 13, + "pended": 1, + "quarantined": 0, + "skipped": 2, + "successful": 15, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "fails at the top-level", + "lineage": [ + "fails at the top-level" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "line": 53 + }, + "attempt": { + "durationInNanoseconds": 314792, + "meta": { + "labels": [] + }, + "status": { + "kind": "failed", + "message": "Expected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func3()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:54 +0xd4" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.079143-05:00", + "finishedAt": "2022-12-14T13:20:51.079458-05:00" + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed", + "message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func3()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:54 +0xd4" + ] + } + } + ] + }, + { + "name": "Bar passes outside a context", + "lineage": [ + "Bar", + "passes outside a context" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "line": 15 + }, + "attempt": { + "durationInNanoseconds": 27833, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.079601-05:00", + "finishedAt": "2022-12-14T13:20:51.079629-05:00" + } + }, + { + "name": "Bar fails outside a context", + "lineage": [ + "Bar", + "fails outside a context" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "line": 19 + }, + "attempt": { + "durationInNanoseconds": 161666, + "meta": { + "labels": [] + }, + "status": { + "kind": "failed", + "message": "Expected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func1.3()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:20 +0xd4" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.079639-05:00", + "finishedAt": "2022-12-14T13:20:51.079801-05:00" + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed", + "message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func1.3()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:20 +0xd4" + ] + } + } + ] + }, + { + "name": "Bar within a context passes", + "lineage": [ + "Bar", + "within a context", + "passes" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "line": 28 + }, + "attempt": { + "durationInNanoseconds": 35416, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.079909-05:00", + "finishedAt": "2022-12-14T13:20:51.079944-05:00" + } + }, + { + "name": "Bar within a context fails", + "lineage": [ + "Bar", + "within a context", + "fails" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "line": 32 + }, + "attempt": { + "durationInNanoseconds": 187750, + "meta": { + "labels": [] + }, + "status": { + "kind": "failed", + "message": "Expected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func1.4.3()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:33 +0xd4" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.079952-05:00", + "finishedAt": "2022-12-14T13:20:51.080139-05:00" + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed", + "message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func1.4.3()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:33 +0xd4" + ] + } + } + ] + }, + { + "name": "Bar failing before each fails even with the test passing", + "lineage": [ + "Bar", + "failing before each", + "fails even with the test passing" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "line": 42 + }, + "attempt": { + "durationInNanoseconds": 201083, + "meta": { + "labels": [] + }, + "status": { + "kind": "failed", + "message": "Expected\n \u003cint\u003e: 1\nto equal\n \u003cint\u003e: 2", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func1.5.1()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:39 +0x90" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.080202-05:00", + "finishedAt": "2022-12-14T13:20:51.080403-05:00" + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed", + "message": "Failure recorded during attempt 1:\nExpected\n \u003cint\u003e: 1\nto equal\n \u003cint\u003e: 2", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func1.5.1()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:39 +0x90" + ] + } + } + ] + }, + { + "name": "Foo passes outside a context", + "lineage": [ + "Foo", + "passes outside a context" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "line": 14 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "labels": [] + }, + "status": { + "kind": "pended" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.080478-05:00", + "finishedAt": "0001-01-01T00:00:00Z" + } + }, + { + "name": "Foo fails while skipped", + "lineage": [ + "Foo", + "fails while skipped" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "line": 18 + }, + "attempt": { + "durationInNanoseconds": 71125, + "meta": { + "labels": [] + }, + "status": { + "kind": "skipped", + "message": "for a reason" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.080484-05:00", + "finishedAt": "2022-12-14T13:20:51.080556-05:00" + } + }, + { + "name": "Foo passes while skipped", + "lineage": [ + "Foo", + "passes while skipped" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "line": 23 + }, + "attempt": { + "durationInNanoseconds": 52000, + "meta": { + "labels": [] + }, + "status": { + "kind": "skipped", + "message": "for a reason" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.080563-05:00", + "finishedAt": "2022-12-14T13:20:51.080615-05:00" + } + }, + { + "name": "Foo within a context passes", + "lineage": [ + "Foo", + "within a context", + "passes" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "line": 29 + }, + "attempt": { + "durationInNanoseconds": 9208, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.080626-05:00", + "finishedAt": "2022-12-14T13:20:51.080635-05:00" + } + }, + { + "name": "Foo within a context fails", + "lineage": [ + "Foo", + "within a context", + "fails" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "line": 33 + }, + "attempt": { + "durationInNanoseconds": 164750, + "meta": { + "labels": [] + }, + "status": { + "kind": "failed", + "message": "Expected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func4.4.2()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go:34 +0xd4" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.08064-05:00", + "finishedAt": "2022-12-14T13:20:51.080805-05:00" + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed", + "message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func4.4.2()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go:34 +0xd4" + ] + } + } + ] + }, + { + "name": "Foo Fooing with different args When a", + "lineage": [ + "Foo", + "Fooing with different args", + "When a" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "line": 41 + }, + "attempt": { + "durationInNanoseconds": 74334, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.080859-05:00", + "finishedAt": "2022-12-14T13:20:51.080934-05:00" + } + }, + { + "name": "Foo Fooing with different args When b", + "lineage": [ + "Foo", + "Fooing with different args", + "When b" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "line": 42 + }, + "attempt": { + "durationInNanoseconds": 7750, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.080943-05:00", + "finishedAt": "2022-12-14T13:20:51.080951-05:00" + } + }, + { + "name": "Foo Fooing with different args When c", + "lineage": [ + "Foo", + "Fooing with different args", + "When c" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "line": 43 + }, + "attempt": { + "durationInNanoseconds": 6292, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.080955-05:00", + "finishedAt": "2022-12-14T13:20:51.080961-05:00" + } + }, + { + "name": "fails slowly at the top-level", + "lineage": [ + "fails slowly at the top-level" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "line": 52 + }, + "attempt": { + "durationInNanoseconds": 3003648416, + "meta": { + "labels": [] + }, + "status": { + "kind": "failed", + "message": "Expected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func6()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go:54 +0xdc" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:51.080965-05:00", + "finishedAt": "2022-12-14T13:20:54.084629-05:00" + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed", + "message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func6()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go:54 +0xdc" + ] + } + } + ] + }, + { + "name": "passes at the top-level", + "lineage": [ + "passes at the top-level" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "line": 48 + }, + "attempt": { + "durationInNanoseconds": 261333, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "passing at the top-level, tada!", + "startedAt": "2022-12-14T13:20:54.085975-05:00", + "finishedAt": "2022-12-14T13:20:54.086236-05:00" + } + }, + { + "name": "passes slowly at the top-level", + "lineage": [ + "passes slowly at the top-level" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "line": 47 + }, + "attempt": { + "durationInNanoseconds": 1501290417, + "meta": { + "labels": [ + "has", + "labels" + ] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:54.086315-05:00", + "finishedAt": "2022-12-14T13:20:55.587613-05:00" + } + }, + { + "name": "fails at the top-level", + "lineage": [ + "fails at the top-level" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "line": 34 + }, + "attempt": { + "durationInNanoseconds": 902250, + "meta": { + "labels": [] + }, + "status": { + "kind": "failed", + "message": "Expected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func3()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:35 +0xd4" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.749969-05:00", + "finishedAt": "2022-12-14T13:20:55.750871-05:00" + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed", + "message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func3()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:35 +0xd4" + ] + } + } + ] + }, + { + "name": "Bar passes outside a context", + "lineage": [ + "Bar", + "passes outside a context" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "line": 11 + }, + "attempt": { + "durationInNanoseconds": 24333, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.751146-05:00", + "finishedAt": "2022-12-14T13:20:55.75117-05:00" + } + }, + { + "name": "Bar fails outside a context", + "lineage": [ + "Bar", + "fails outside a context" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "line": 15 + }, + "attempt": { + "durationInNanoseconds": 370250, + "meta": { + "labels": [] + }, + "status": { + "kind": "failed", + "message": "Expected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func1.2()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:16 +0xd4" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.751191-05:00", + "finishedAt": "2022-12-14T13:20:55.751562-05:00" + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed", + "message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func1.2()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:16 +0xd4" + ] + } + } + ] + }, + { + "name": "Bar within a context passes", + "lineage": [ + "Bar", + "within a context", + "passes" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "line": 20 + }, + "attempt": { + "durationInNanoseconds": 31375, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.751959-05:00", + "finishedAt": "2022-12-14T13:20:55.751991-05:00" + } + }, + { + "name": "Bar within a context fails", + "lineage": [ + "Bar", + "within a context", + "fails" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "line": 24 + }, + "attempt": { + "durationInNanoseconds": 390125, + "meta": { + "labels": [] + }, + "status": { + "kind": "failed", + "message": "Expected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func1.3.2()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:25 +0xd4" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.752172-05:00", + "finishedAt": "2022-12-14T13:20:55.752562-05:00" + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed", + "message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func1.3.2()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:25 +0xd4" + ] + } + } + ] + }, + { + "name": "Foo fails then passes", + "lineage": [ + "Foo", + "fails then passes" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "line": 14 + }, + "attempt": { + "durationInNanoseconds": 952667, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.752989-05:00", + "finishedAt": "2022-12-14T13:20:55.753942-05:00" + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed", + "message": "Failure recorded during attempt 1:\nuh oh", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func4.1()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:18 +0x124" + ] + } + } + ] + }, + { + "name": "Foo passes then fails", + "lineage": [ + "Foo", + "passes then fails" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "line": 24 + }, + "attempt": { + "durationInNanoseconds": 417125, + "meta": { + "labels": [] + }, + "status": { + "kind": "failed", + "message": "uh oh", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func4.2()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:30 +0x44" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.753993-05:00", + "finishedAt": "2022-12-14T13:20:55.75441-05:00" + } + }, + { + "name": "Foo passes outside a context", + "lineage": [ + "Foo", + "passes outside a context" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "line": 34 + }, + "attempt": { + "durationInNanoseconds": 30417, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.755427-05:00", + "finishedAt": "2022-12-14T13:20:55.755457-05:00" + } + }, + { + "name": "Foo fails outside a context", + "lineage": [ + "Foo", + "fails outside a context" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "line": 38 + }, + "attempt": { + "durationInNanoseconds": 229208, + "meta": { + "labels": [] + }, + "status": { + "kind": "failed", + "message": "Expected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func4.4()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:39 +0xd4" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.755472-05:00", + "finishedAt": "2022-12-14T13:20:55.755701-05:00" + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed", + "message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func4.4()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:39 +0xd4" + ] + } + } + ] + }, + { + "name": "Foo within a context passes", + "lineage": [ + "Foo", + "within a context", + "passes" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "line": 43 + }, + "attempt": { + "durationInNanoseconds": 12042, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.75577-05:00", + "finishedAt": "2022-12-14T13:20:55.755782-05:00" + } + }, + { + "name": "Foo within a context fails", + "lineage": [ + "Foo", + "within a context", + "fails" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "line": 47 + }, + "attempt": { + "durationInNanoseconds": 227958, + "meta": { + "labels": [] + }, + "status": { + "kind": "failed", + "message": "Expected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func4.5.2()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:48 +0xd4" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.755789-05:00", + "finishedAt": "2022-12-14T13:20:55.756017-05:00" + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed", + "message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func4.5.2()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:48 +0xd4" + ] + } + } + ] + }, + { + "name": "fails at the top-level", + "lineage": [ + "fails at the top-level" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "line": 57 + }, + "attempt": { + "durationInNanoseconds": 170208, + "meta": { + "labels": [] + }, + "status": { + "kind": "failed", + "message": "Expected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func6()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:58 +0xd4" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.756113-05:00", + "finishedAt": "2022-12-14T13:20:55.756283-05:00" + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed", + "message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "backtrace": [ + "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func6()", + "\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:58 +0xd4" + ] + } + } + ] + }, + { + "name": "passes at the top-level", + "lineage": [ + "passes at the top-level" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "line": 30 + }, + "attempt": { + "durationInNanoseconds": 10708, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.756339-05:00", + "finishedAt": "2022-12-14T13:20:55.75635-05:00" + } + }, + { + "name": "passes at the top-level", + "lineage": [ + "passes at the top-level" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "line": 53 + }, + "attempt": { + "durationInNanoseconds": 9625, + "meta": { + "labels": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-14T13:20:55.756357-05:00", + "finishedAt": "2022-12-14T13:20:55.756366-05:00" + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/GoTestParser Parse parses the sample file b/internal/captain/parsing/.snapshots/GoTestParser Parse parses the sample file new file mode 100644 index 0000000..808d626 --- /dev/null +++ b/internal/captain/parsing/.snapshots/GoTestParser Parse parses the sample file @@ -0,0 +1,516 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Go", + "kind": "go test" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 35, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 9, + "pended": 0, + "quarantined": 0, + "skipped": 2, + "successful": 24, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "FuzzHex", + "scope": "github.com/captain-examples/go-testing/internal/pkg2", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg2" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN FuzzHex\n--- PASS: FuzzHex (0.00s)\n" + } + }, + { + "name": "FuzzHex/seed#0", + "scope": "github.com/captain-examples/go-testing/internal/pkg2", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg2" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN FuzzHex/seed#0\n --- PASS: FuzzHex/seed#0 (0.00s)\n" + } + }, + { + "name": "FuzzHex/seed#1", + "scope": "github.com/captain-examples/go-testing/internal/pkg2", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg2" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN FuzzHex/seed#1\n --- PASS: FuzzHex/seed#1 (0.00s)\n" + } + }, + { + "name": "FuzzHex/seed#2", + "scope": "github.com/captain-examples/go-testing/internal/pkg2", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg2" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN FuzzHex/seed#2\n --- PASS: FuzzHex/seed#2 (0.00s)\n" + } + }, + { + "name": "FuzzHex/seed#3", + "scope": "github.com/captain-examples/go-testing/internal/pkg2", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg2" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN FuzzHex/seed#3\n --- PASS: FuzzHex/seed#3 (0.00s)\n" + } + }, + { + "name": "FuzzHex/seed#4", + "scope": "github.com/captain-examples/go-testing/internal/pkg2", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg2" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN FuzzHex/seed#4\n --- PASS: FuzzHex/seed#4 (0.00s)\n" + } + }, + { + "name": "FuzzHex/seed#5", + "scope": "github.com/captain-examples/go-testing/internal/pkg2", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg2" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN FuzzHex/seed#5\n --- PASS: FuzzHex/seed#5 (0.00s)\n" + } + }, + { + "name": "TestBar", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestBar\n--- PASS: TestBar (0.00s)\n" + } + }, + { + "name": "TestBarFailing", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "failed" + }, + "stdout": "=== RUN TestBarFailing\n bar_test.go:18: Bar: arg != Bar: not arg\n--- FAIL: TestBarFailing (0.00s)\n" + } + }, + { + "name": "TestFoo", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFoo\n--- PASS: TestFoo (0.00s)\n" + } + }, + { + "name": "TestFoo", + "scope": "github.com/captain-examples/go-testing/internal/pkg2", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg2" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFoo\n--- PASS: TestFoo (0.00s)\n" + } + }, + { + "name": "TestFooFailing", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "failed" + }, + "stdout": "=== RUN TestFooFailing\n foo_test.go:18: Foo: arg != Foo: not arg\n--- FAIL: TestFooFailing (0.00s)\n" + } + }, + { + "name": "TestFooParallelTable", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFooParallelTable\n--- PASS: TestFooParallelTable (0.00s)\n" + } + }, + { + "name": "TestFooParallelTable/0", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFooParallelTable/0\n=== PAUSE TestFooParallelTable/0\n=== CONT TestFooParallelTable/0\n foo_test.go:46: running in parallel 1\n foo_test.go:47: running in parallel 2\n foo_test.go:48: running in parallel 3\n foo_test.go:49: running in parallel 4\n foo_test.go:50: running in parallel 5\n --- PASS: TestFooParallelTable/0 (0.00s)\n" + } + }, + { + "name": "TestFooParallelTable/1", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFooParallelTable/1\n=== PAUSE TestFooParallelTable/1\n=== CONT TestFooParallelTable/1\n foo_test.go:46: running in parallel 1\n foo_test.go:47: running in parallel 2\n=== CONT TestFooParallelTable/1\n foo_test.go:48: running in parallel 3\n foo_test.go:49: running in parallel 4\n=== CONT TestFooParallelTable/1\n foo_test.go:50: running in parallel 5\n --- PASS: TestFooParallelTable/1 (0.00s)\n" + } + }, + { + "name": "TestFooParallelTable/2", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFooParallelTable/2\n=== PAUSE TestFooParallelTable/2\n=== CONT TestFooParallelTable/2\n foo_test.go:46: running in parallel 1\n foo_test.go:47: running in parallel 2\n foo_test.go:48: running in parallel 3\n=== CONT TestFooParallelTable/2\n foo_test.go:49: running in parallel 4\n foo_test.go:50: running in parallel 5\n --- PASS: TestFooParallelTable/2 (0.00s)\n" + } + }, + { + "name": "TestFooParallelTable/3", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFooParallelTable/3\n=== PAUSE TestFooParallelTable/3\n=== CONT TestFooParallelTable/3\n=== CONT TestFooParallelTable/3\n foo_test.go:46: running in parallel 1\n foo_test.go:47: running in parallel 2\n foo_test.go:48: running in parallel 3\n foo_test.go:49: running in parallel 4\n foo_test.go:50: running in parallel 5\n --- PASS: TestFooParallelTable/3 (0.00s)\n" + } + }, + { + "name": "TestFooParallelTable/4", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFooParallelTable/4\n=== PAUSE TestFooParallelTable/4\n=== CONT TestFooParallelTable/4\n foo_test.go:46: running in parallel 1\n foo_test.go:47: running in parallel 2\n foo_test.go:48: running in parallel 3\n foo_test.go:49: running in parallel 4\n foo_test.go:50: running in parallel 5\n --- PASS: TestFooParallelTable/4 (0.00s)\n" + } + }, + { + "name": "TestFooTable", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFooTable\n--- PASS: TestFooTable (0.00s)\n" + } + }, + { + "name": "TestFooTable/0", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFooTable/0\n foo_test.go:25: running 1\n foo_test.go:26: running 2\n foo_test.go:27: running 3\n foo_test.go:28: running 4\n foo_test.go:29: running 5\n --- PASS: TestFooTable/0 (0.00s)\n" + } + }, + { + "name": "TestFooTable/1", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFooTable/1\n foo_test.go:25: running 1\n foo_test.go:26: running 2\n foo_test.go:27: running 3\n foo_test.go:28: running 4\n foo_test.go:29: running 5\n --- PASS: TestFooTable/1 (0.00s)\n" + } + }, + { + "name": "TestFooTable/2", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFooTable/2\n foo_test.go:25: running 1\n foo_test.go:26: running 2\n foo_test.go:27: running 3\n foo_test.go:28: running 4\n foo_test.go:29: running 5\n --- PASS: TestFooTable/2 (0.00s)\n" + } + }, + { + "name": "TestFooTable/3", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFooTable/3\n foo_test.go:25: running 1\n foo_test.go:26: running 2\n foo_test.go:27: running 3\n foo_test.go:28: running 4\n foo_test.go:29: running 5\n --- PASS: TestFooTable/3 (0.00s)\n" + } + }, + { + "name": "TestFooTable/4", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestFooTable/4\n foo_test.go:25: running 1\n foo_test.go:26: running 2\n foo_test.go:27: running 3\n foo_test.go:28: running 4\n foo_test.go:29: running 5\n --- PASS: TestFooTable/4 (0.00s)\n" + } + }, + { + "name": "TestSkipNoReason", + "scope": "github.com/captain-examples/go-testing/internal/pkg2", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg2" + }, + "status": { + "kind": "skipped" + }, + "stdout": "=== RUN TestSkipNoReason\n other_test.go:64: \n--- SKIP: TestSkipNoReason (0.00s)\n" + } + }, + { + "name": "TestSkipReason", + "scope": "github.com/captain-examples/go-testing/internal/pkg2", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg2" + }, + "status": { + "kind": "skipped" + }, + "stdout": "=== RUN TestSkipReason\n other_test.go:68: for a reason\n--- SKIP: TestSkipReason (0.00s)\n" + } + }, + { + "name": "TestSlow", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 1500000000, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestSlow\n--- PASS: TestSlow (1.50s)\n" + } + }, + { + "name": "TestTableFailing", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "failed" + }, + "stdout": "=== RUN TestTableFailing\n--- FAIL: TestTableFailing (0.00s)\n" + } + }, + { + "name": "TestTableFailing/0", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "failed" + }, + "stdout": "=== RUN TestTableFailing/0\n foo_test.go:37: failed\n --- FAIL: TestTableFailing/0 (0.00s)\n" + } + }, + { + "name": "TestTableFailing/1", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "failed" + }, + "stdout": "=== RUN TestTableFailing/1\n foo_test.go:37: failed\n --- FAIL: TestTableFailing/1 (0.00s)\n" + } + }, + { + "name": "TestTableFailing/2", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "failed" + }, + "stdout": "=== RUN TestTableFailing/2\n foo_test.go:37: failed\n --- FAIL: TestTableFailing/2 (0.00s)\n" + } + }, + { + "name": "TestTableFailing/3", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "failed" + }, + "stdout": "=== RUN TestTableFailing/3\n foo_test.go:37: failed\n --- FAIL: TestTableFailing/3 (0.00s)\n" + } + }, + { + "name": "TestTableFailing/4", + "scope": "github.com/captain-examples/go-testing/internal/pkg1", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg1" + }, + "status": { + "kind": "failed" + }, + "stdout": "=== RUN TestTableFailing/4\n foo_test.go:37: failed\n --- FAIL: TestTableFailing/4 (0.00s)\n" + } + }, + { + "name": "TestWhatever", + "scope": "github.com/captain-examples/go-testing/internal/pkg2", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg2" + }, + "status": { + "kind": "successful" + }, + "stdout": "=== RUN TestWhatever\n--- PASS: TestWhatever (0.00s)\n" + } + }, + { + "name": "TestWhateverFailing", + "scope": "github.com/captain-examples/go-testing/internal/pkg2", + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "package": "github.com/captain-examples/go-testing/internal/pkg2" + }, + "status": { + "kind": "failed" + }, + "stdout": "=== RUN TestWhateverFailing\n other_test.go:25: Foo: arg != Foo: not arg\n--- FAIL: TestWhateverFailing (0.00s)\n" + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/JUnitTestsuiteParser Parse parses the sample testsuite file b/internal/captain/parsing/.snapshots/JUnitTestsuiteParser Parse parses the sample testsuite file new file mode 100644 index 0000000..43c4d33 --- /dev/null +++ b/internal/captain/parsing/.snapshots/JUnitTestsuiteParser Parse parses the sample testsuite file @@ -0,0 +1,690 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "other", + "kind": "other" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 72, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 3, + "pended": 0, + "quarantined": 0, + "skipped": 2, + "successful": 67, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "reporting::test_dot_reporter::breaks_lines_with_many_dots", + "attempt": { + "durationInNanoseconds": 352000000, + "status": { + "kind": "failed", + "message": "Failed because of some assertion", + "exception": "failure", + "backtrace": [ + "" + ] + } + } + }, + { + "name": "reporting::test_dot_reporter::dot_line_limit_fits_on_one_line", + "attempt": { + "durationInNanoseconds": 380000000, + "status": { + "kind": "failed", + "message": "Some error occurred", + "exception": "error", + "backtrace": [ + "" + ] + } + } + }, + { + "name": "reporting::test_dot_reporter::flush_buffer_when_test_results_done", + "attempt": { + "durationInNanoseconds": 353000000, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "reporting::test_dot_reporter::formats_results_as_dots_with_summary", + "attempt": { + "durationInNanoseconds": 325000000, + "status": { + "kind": "failed", + "message": "Multiple", + "exception": "failure", + "backtrace": [ + "" + ] + } + } + }, + { + "name": "reporting::test_dot_reporter::one_newline_before_summaries_line_limit_reached", + "attempt": { + "durationInNanoseconds": 345000000, + "status": { + "kind": "skipped", + "message": "For some reason" + } + } + }, + { + "name": "reporting::test_dot_reporter::one_newline_before_summaries_line_limit_not_reached", + "attempt": { + "durationInNanoseconds": 381000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_dot_reporter::write_on_result", + "attempt": { + "durationInNanoseconds": 279000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_line_reporter::flush_buffer_when_test_results_done", + "attempt": { + "durationInNanoseconds": 291000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_line_reporter::formats_results_as_lines", + "attempt": { + "durationInNanoseconds": 295000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_line_reporter::write_on_result", + "attempt": { + "durationInNanoseconds": 253000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_dot_reporter", + "attempt": { + "durationInNanoseconds": 298000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_junit_reporter_eq_no_suffix_to_default", + "attempt": { + "durationInNanoseconds": 318000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_junit_reporter_eq_no_suffix_with_spaces_to_default", + "attempt": { + "durationInNanoseconds": 254000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_junit_reporter_eq_with_suffix_to_absolute_path", + "attempt": { + "durationInNanoseconds": 274000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_junit_reporter_eq_with_suffix_to_relative_path", + "attempt": { + "durationInNanoseconds": 267000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_junit_reporter_no_eq_to_default", + "attempt": { + "durationInNanoseconds": 277000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_line_reporter", + "attempt": { + "durationInNanoseconds": 257000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_unknown_reporter", + "attempt": { + "durationInNanoseconds": 250000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::validate_multiple_args", + "attempt": { + "durationInNanoseconds": 247000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::validate_one_test_arg", + "attempt": { + "durationInNanoseconds": 253000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::validate_test_args_empty", + "attempt": { + "durationInNanoseconds": 261000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "healthcheck_queue_failure", + "attempt": { + "durationInNanoseconds": 264000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "healthcheck_queue_success", + "attempt": { + "durationInNanoseconds": 291000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue_open_servers_on_specified_ports", + "attempt": { + "durationInNanoseconds": 281000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test_validate_protocol_version_message::protocol_version_message_incompatible", + "attempt": { + "durationInNanoseconds": 242000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test_validate_protocol_version_message::protocol_version_message_recv_wrong_message", + "attempt": { + "durationInNanoseconds": 251000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test_validate_protocol_version_message::protocol_version_message_tunnel_dropped", + "attempt": { + "durationInNanoseconds": 240000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test_validate_protocol_version_message::protocol_version_message_tunnel_timeout", + "attempt": { + "durationInNanoseconds": 347000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test_validate_protocol_version_message::recv_and_validate_protocol_version_message", + "attempt": { + "durationInNanoseconds": 214000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::error_with_empty_output_prints_empty_output", + "attempt": { + "durationInNanoseconds": 319000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::extend_appends_tests", + "attempt": { + "durationInNanoseconds": 315000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::failure_with_empty_output_prints_empty_output", + "attempt": { + "durationInNanoseconds": 295000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::generates_junit_xml_for_all_statuses", + "attempt": { + "durationInNanoseconds": 300000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_exact_millis", + "attempt": { + "durationInNanoseconds": 302000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_exact_minutes", + "attempt": { + "durationInNanoseconds": 296000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_exact_minutes_leftover_seconds_and_millis", + "attempt": { + "durationInNanoseconds": 311000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_exact_seconds", + "attempt": { + "durationInNanoseconds": 315000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_exact_seconds_leftover_millis", + "attempt": { + "durationInNanoseconds": 313000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_line_error", + "attempt": { + "durationInNanoseconds": 315000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_line_failure", + "attempt": { + "durationInNanoseconds": 295000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_line_pending", + "attempt": { + "durationInNanoseconds": 297000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_line_skipped", + "attempt": { + "durationInNanoseconds": 293000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_line_success", + "attempt": { + "durationInNanoseconds": 295000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_error", + "attempt": { + "durationInNanoseconds": 305000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_failure", + "attempt": { + "durationInNanoseconds": 314000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_multiline", + "attempt": { + "durationInNanoseconds": 304000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_no_output", + "attempt": { + "durationInNanoseconds": 299000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_pending", + "attempt": { + "durationInNanoseconds": 291000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_skipped", + "attempt": { + "durationInNanoseconds": 288000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_success", + "attempt": { + "durationInNanoseconds": 299000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "procspawn_test_helper", + "attempt": { + "durationInNanoseconds": 246000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::bad_message_doesnt_take_down_work_scheduling_server", + "attempt": { + "durationInNanoseconds": 303000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::bad_message_doesnt_take_down_server", + "attempt": { + "durationInNanoseconds": 345000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::client_disconnect_then_connect_gets_buffered_results", + "attempt": { + "durationInNanoseconds": 320000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::invoker_reconnection_fails_never_invoked", + "attempt": { + "durationInNanoseconds": 309000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::invoker_reconnection_succeeds", + "attempt": { + "durationInNanoseconds": 231000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::multiple_invokers", + "attempt": { + "durationInNanoseconds": 288000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::multiple_jobs_complete", + "attempt": { + "durationInNanoseconds": 289000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::queue_server_healthcheck", + "attempt": { + "durationInNanoseconds": 240000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "net_protocol::test::error_reads_that_are_too_large", + "attempt": { + "durationInNanoseconds": 261000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::work_server_healthcheck", + "attempt": { + "durationInNanoseconds": 310000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "net_protocol::test::error_reads_that_are_too_large_async", + "attempt": { + "durationInNanoseconds": 332000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "negotiate::test::queue_and_workers_lifecycle", + "attempt": { + "durationInNanoseconds": 323000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "procspawn_test_helper", + "attempt": { + "durationInNanoseconds": 309000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "negotiate::test::queue_negotiator_healthcheck", + "attempt": { + "durationInNanoseconds": 346000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::test_1_worker_1_echo", + "attempt": { + "durationInNanoseconds": 298000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::bad_message_doesnt_take_down_queue_negotiator_server", + "attempt": { + "durationInNanoseconds": 354000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::test_1_worker_2_echos", + "attempt": { + "durationInNanoseconds": 311000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::test_2_workers_1_echo", + "attempt": { + "durationInNanoseconds": 318000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::test_2_workers_2_echos", + "attempt": { + "durationInNanoseconds": 293000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::test_2_workers_8_echos", + "attempt": { + "durationInNanoseconds": 371000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::work_in_constant_context", + "attempt": { + "durationInNanoseconds": 269000000, + "status": { + "kind": "successful" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/JUnitTestsuitesParser Parse parses the sample testsuites file b/internal/captain/parsing/.snapshots/JUnitTestsuitesParser Parse parses the sample testsuites file new file mode 100644 index 0000000..43c4d33 --- /dev/null +++ b/internal/captain/parsing/.snapshots/JUnitTestsuitesParser Parse parses the sample testsuites file @@ -0,0 +1,690 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "other", + "kind": "other" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 72, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 3, + "pended": 0, + "quarantined": 0, + "skipped": 2, + "successful": 67, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "reporting::test_dot_reporter::breaks_lines_with_many_dots", + "attempt": { + "durationInNanoseconds": 352000000, + "status": { + "kind": "failed", + "message": "Failed because of some assertion", + "exception": "failure", + "backtrace": [ + "" + ] + } + } + }, + { + "name": "reporting::test_dot_reporter::dot_line_limit_fits_on_one_line", + "attempt": { + "durationInNanoseconds": 380000000, + "status": { + "kind": "failed", + "message": "Some error occurred", + "exception": "error", + "backtrace": [ + "" + ] + } + } + }, + { + "name": "reporting::test_dot_reporter::flush_buffer_when_test_results_done", + "attempt": { + "durationInNanoseconds": 353000000, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "reporting::test_dot_reporter::formats_results_as_dots_with_summary", + "attempt": { + "durationInNanoseconds": 325000000, + "status": { + "kind": "failed", + "message": "Multiple", + "exception": "failure", + "backtrace": [ + "" + ] + } + } + }, + { + "name": "reporting::test_dot_reporter::one_newline_before_summaries_line_limit_reached", + "attempt": { + "durationInNanoseconds": 345000000, + "status": { + "kind": "skipped", + "message": "For some reason" + } + } + }, + { + "name": "reporting::test_dot_reporter::one_newline_before_summaries_line_limit_not_reached", + "attempt": { + "durationInNanoseconds": 381000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_dot_reporter::write_on_result", + "attempt": { + "durationInNanoseconds": 279000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_line_reporter::flush_buffer_when_test_results_done", + "attempt": { + "durationInNanoseconds": 291000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_line_reporter::formats_results_as_lines", + "attempt": { + "durationInNanoseconds": 295000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_line_reporter::write_on_result", + "attempt": { + "durationInNanoseconds": 253000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_dot_reporter", + "attempt": { + "durationInNanoseconds": 298000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_junit_reporter_eq_no_suffix_to_default", + "attempt": { + "durationInNanoseconds": 318000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_junit_reporter_eq_no_suffix_with_spaces_to_default", + "attempt": { + "durationInNanoseconds": 254000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_junit_reporter_eq_with_suffix_to_absolute_path", + "attempt": { + "durationInNanoseconds": 274000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_junit_reporter_eq_with_suffix_to_relative_path", + "attempt": { + "durationInNanoseconds": 267000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_junit_reporter_no_eq_to_default", + "attempt": { + "durationInNanoseconds": 277000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_line_reporter", + "attempt": { + "durationInNanoseconds": 257000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "reporting::test_reporter_kind::parse_unknown_reporter", + "attempt": { + "durationInNanoseconds": 250000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::validate_multiple_args", + "attempt": { + "durationInNanoseconds": 247000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::validate_one_test_arg", + "attempt": { + "durationInNanoseconds": 253000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::validate_test_args_empty", + "attempt": { + "durationInNanoseconds": 261000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "healthcheck_queue_failure", + "attempt": { + "durationInNanoseconds": 264000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "healthcheck_queue_success", + "attempt": { + "durationInNanoseconds": 291000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue_open_servers_on_specified_ports", + "attempt": { + "durationInNanoseconds": 281000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test_validate_protocol_version_message::protocol_version_message_incompatible", + "attempt": { + "durationInNanoseconds": 242000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test_validate_protocol_version_message::protocol_version_message_recv_wrong_message", + "attempt": { + "durationInNanoseconds": 251000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test_validate_protocol_version_message::protocol_version_message_tunnel_dropped", + "attempt": { + "durationInNanoseconds": 240000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test_validate_protocol_version_message::protocol_version_message_tunnel_timeout", + "attempt": { + "durationInNanoseconds": 347000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test_validate_protocol_version_message::recv_and_validate_protocol_version_message", + "attempt": { + "durationInNanoseconds": 214000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::error_with_empty_output_prints_empty_output", + "attempt": { + "durationInNanoseconds": 319000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::extend_appends_tests", + "attempt": { + "durationInNanoseconds": 315000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::failure_with_empty_output_prints_empty_output", + "attempt": { + "durationInNanoseconds": 295000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::generates_junit_xml_for_all_statuses", + "attempt": { + "durationInNanoseconds": 300000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_exact_millis", + "attempt": { + "durationInNanoseconds": 302000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_exact_minutes", + "attempt": { + "durationInNanoseconds": 296000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_exact_minutes_leftover_seconds_and_millis", + "attempt": { + "durationInNanoseconds": 311000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_exact_seconds", + "attempt": { + "durationInNanoseconds": 315000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_exact_seconds_leftover_millis", + "attempt": { + "durationInNanoseconds": 313000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_line_error", + "attempt": { + "durationInNanoseconds": 315000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_line_failure", + "attempt": { + "durationInNanoseconds": 295000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_line_pending", + "attempt": { + "durationInNanoseconds": 297000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_line_skipped", + "attempt": { + "durationInNanoseconds": 293000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_line_success", + "attempt": { + "durationInNanoseconds": 295000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_error", + "attempt": { + "durationInNanoseconds": 305000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_failure", + "attempt": { + "durationInNanoseconds": 314000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_multiline", + "attempt": { + "durationInNanoseconds": 304000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_no_output", + "attempt": { + "durationInNanoseconds": 299000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_pending", + "attempt": { + "durationInNanoseconds": 291000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_skipped", + "attempt": { + "durationInNanoseconds": 288000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "test::format_summary_success", + "attempt": { + "durationInNanoseconds": 299000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "procspawn_test_helper", + "attempt": { + "durationInNanoseconds": 246000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::bad_message_doesnt_take_down_work_scheduling_server", + "attempt": { + "durationInNanoseconds": 303000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::bad_message_doesnt_take_down_server", + "attempt": { + "durationInNanoseconds": 345000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::client_disconnect_then_connect_gets_buffered_results", + "attempt": { + "durationInNanoseconds": 320000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::invoker_reconnection_fails_never_invoked", + "attempt": { + "durationInNanoseconds": 309000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::invoker_reconnection_succeeds", + "attempt": { + "durationInNanoseconds": 231000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::multiple_invokers", + "attempt": { + "durationInNanoseconds": 288000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::multiple_jobs_complete", + "attempt": { + "durationInNanoseconds": 289000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::queue_server_healthcheck", + "attempt": { + "durationInNanoseconds": 240000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "net_protocol::test::error_reads_that_are_too_large", + "attempt": { + "durationInNanoseconds": 261000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "queue::test::work_server_healthcheck", + "attempt": { + "durationInNanoseconds": 310000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "net_protocol::test::error_reads_that_are_too_large_async", + "attempt": { + "durationInNanoseconds": 332000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "negotiate::test::queue_and_workers_lifecycle", + "attempt": { + "durationInNanoseconds": 323000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "procspawn_test_helper", + "attempt": { + "durationInNanoseconds": 309000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "negotiate::test::queue_negotiator_healthcheck", + "attempt": { + "durationInNanoseconds": 346000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::test_1_worker_1_echo", + "attempt": { + "durationInNanoseconds": 298000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::bad_message_doesnt_take_down_queue_negotiator_server", + "attempt": { + "durationInNanoseconds": 354000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::test_1_worker_2_echos", + "attempt": { + "durationInNanoseconds": 311000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::test_2_workers_1_echo", + "attempt": { + "durationInNanoseconds": 318000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::test_2_workers_2_echos", + "attempt": { + "durationInNanoseconds": 293000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::test_2_workers_8_echos", + "attempt": { + "durationInNanoseconds": 371000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "workers::test::work_in_constant_context", + "attempt": { + "durationInNanoseconds": 269000000, + "status": { + "kind": "successful" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/JavaScriptCucumberJSONParser Parse parses the sample file b/internal/captain/parsing/.snapshots/JavaScriptCucumberJSONParser Parse parses the sample file new file mode 100644 index 0000000..3a6820a --- /dev/null +++ b/internal/captain/parsing/.snapshots/JavaScriptCucumberJSONParser Parse parses the sample file @@ -0,0 +1,248 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "JavaScript", + "kind": "Cucumber" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 7, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 3, + "pended": 0, + "quarantined": 0, + "skipped": 0, + "successful": 1, + "timedOut": 0, + "todo": 3 + }, + "tests": [ + { + "name": "Rule Sample \u003e A passing example", + "lineage": [ + "Rule Sample", + "A passing example" + ], + "location": { + "file": "features/rule.feature", + "line": 7 + }, + "attempt": { + "durationInNanoseconds": 179957, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": null + } + ] + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "Rule Sample \u003e A failing example", + "lineage": [ + "Rule Sample", + "A failing example" + ], + "location": { + "file": "features/rule.feature", + "line": 14 + }, + "attempt": { + "durationInNanoseconds": 109040, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": null + }, + { + "name": "@flaky", + "line": 12 + }, + { + "name": "@failing", + "line": 13 + } + ] + }, + "status": { + "kind": "failed", + "message": "AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:\n\n assert(this.this_will_pass === true)\n\n + expected - actual\n\n -false\n +true\n\n at World.\u003canonymous\u003e (/Users/kylekthompson/src/captain-examples/cucumber-js/features/support/steps.js:15:3)" + } + } + }, + { + "name": "Rule Sample \u003e A pending example", + "lineage": [ + "Rule Sample", + "A pending example" + ], + "location": { + "file": "features/rule.feature", + "line": 19 + }, + "attempt": { + "durationInNanoseconds": 22791, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": null + } + ] + }, + "status": { + "kind": "todo" + } + } + }, + { + "name": "Rule Sample \u003e A skipped example", + "lineage": [ + "Rule Sample", + "A skipped example" + ], + "location": { + "file": "features/rule.feature", + "line": 24 + }, + "attempt": { + "durationInNanoseconds": 91374, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": null + } + ] + }, + "status": { + "kind": "todo" + } + } + }, + { + "name": "Rule Sample \u003e An undefined example", + "lineage": [ + "Rule Sample", + "An undefined example" + ], + "location": { + "file": "features/rule.feature", + "line": 29 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": null + } + ] + }, + "status": { + "kind": "todo" + } + } + }, + { + "name": "Rule Sample \u003e With a failing before hook", + "lineage": [ + "Rule Sample", + "With a failing before hook" + ], + "location": { + "file": "features/rule.feature", + "line": 35 + }, + "attempt": { + "durationInNanoseconds": 104333, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": null + }, + { + "name": "@failing_before_hook", + "line": 34 + } + ] + }, + "status": { + "kind": "failed", + "message": "Error: failed in before hook\n at World.\u003canonymous\u003e (/Users/kylekthompson/src/captain-examples/cucumber-js/features/support/steps.js:27:9)" + } + } + }, + { + "name": "Rule Sample \u003e With a failing after hook", + "lineage": [ + "Rule Sample", + "With a failing after hook" + ], + "location": { + "file": "features/rule.feature", + "line": 41 + }, + "attempt": { + "durationInNanoseconds": 118291, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": null + }, + { + "name": "@failing_after_hook", + "line": 40 + } + ] + }, + "status": { + "kind": "failed", + "message": "Error: failed in after hook\n at World.\u003canonymous\u003e (/Users/kylekthompson/src/captain-examples/cucumber-js/features/support/steps.js:31:9)" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/JavaScriptCypressParser Parse parses the sample file b/internal/captain/parsing/.snapshots/JavaScriptCypressParser Parse parses the sample file new file mode 100644 index 0000000..9f95643 --- /dev/null +++ b/internal/captain/parsing/.snapshots/JavaScriptCypressParser Parse parses the sample file @@ -0,0 +1,295 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "JavaScript", + "kind": "Cypress" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 19, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 3, + "pended": 0, + "quarantined": 0, + "skipped": 2, + "successful": 14, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "displays two todo items by default", + "location": { + "file": "cypress/e2e/todo-one.cy.js" + }, + "attempt": { + "durationInNanoseconds": 490000000, + "meta": { + "other-key": "1", + "some-key": "some-value" + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "example to-do app one displays two todo items by default", + "location": { + "file": "cypress/e2e/todo-one.cy.js" + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "failed", + "message": "Timed out retrying after 4000ms: expected '\u003cli\u003e' to have text 'Walk the cat', but the text was 'Walk the dog'", + "exception": "AssertionError", + "backtrace": [ + "at Context.eval (webpack:///./cypress/e2e/todo-one.cy.js:59:35)", + "", + "+ expected - actual", + "", + "-'Walk the dog'", + "+'Walk the cat'", + "" + ] + }, + "stderr": "line 1\n line 2\n line 3" + } + }, + { + "name": "example to-do app one can add new todo items", + "location": { + "file": "cypress/e2e/todo-one.cy.js" + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "failed", + "message": "uh oh something broke", + "exception": "Error", + "backtrace": [ + "at Context.eval (webpack:///./cypress/e2e/todo-one.cy.js:73:10)" + ] + } + } + }, + { + "name": "example to-do app one can check off an item as completed", + "location": { + "file": "cypress/e2e/todo-one.cy.js" + }, + "attempt": { + "durationInNanoseconds": 232000000, + "status": { + "kind": "successful" + }, + "stdout": "line 1\n line 2\n line 3" + } + }, + { + "name": "example to-do app one with a checked task can filter for uncompleted tasks", + "location": { + "file": "cypress/e2e/todo-one.cy.js" + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "failed", + "message": "not a custom error", + "exception": "Error", + "backtrace": [ + "at $Cy.fail (https://example.cypress.io/__cypress/runner/cypress_runner.js:157093:13)", + "at runnable.fn (https://example.cypress.io/__cypress/runner/cypress_runner.js:157707:19)", + "at callFn (https://example.cypress.io/__cypress/runner/cypress_runner.js:107910:21)", + "at ../driver/node_modules/mocha/lib/runnable.js.Runnable.run (https://example.cypress.io/__cypress/runner/cypress_runner.js:107897:7)", + "at \u003cunknown\u003e (https://example.cypress.io/__cypress/runner/cypress_runner.js:164958:30)", + "at PassThroughHandlerContext.finallyHandler (https://example.cypress.io/__cypress/runner/cypress_runner.js:7872:23)", + "at PassThroughHandlerContext.tryCatcher (https://example.cypress.io/__cypress/runner/cypress_runner.js:11318:23)", + "at Promise._settlePromiseFromHandler (https://example.cypress.io/__cypress/runner/cypress_runner.js:9253:31)", + "at Promise._settlePromise (https://example.cypress.io/__cypress/runner/cypress_runner.js:9310:18)", + "at Promise._settlePromise0 (https://example.cypress.io/__cypress/runner/cypress_runner.js:9355:10)", + "at Promise._settlePromises (https://example.cypress.io/__cypress/runner/cypress_runner.js:9435:18)", + "at _drainQueueStep (https://example.cypress.io/__cypress/runner/cypress_runner.js:6025:12)", + "at _drainQueue (https://example.cypress.io/__cypress/runner/cypress_runner.js:6018:9)", + "at ../../node_modules/bluebird/js/release/async.js.Async._drainQueues (https://example.cypress.io/__cypress/runner/cypress_runner.js:6034:5)", + "at Async.drainQueues (https://example.cypress.io/__cypress/runner/cypress_runner.js:5904:14)" + ] + } + } + }, + { + "name": "example to-do app one with a checked task can filter for completed tasks", + "location": { + "file": "cypress/e2e/todo-one.cy.js" + }, + "attempt": { + "durationInNanoseconds": 307000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "example to-do app one with a checked task can delete all completed tasks", + "location": { + "file": "cypress/e2e/todo-one.cy.js" + }, + "attempt": { + "durationInNanoseconds": 308000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "example to-do app one with a checked task more nesting can filter for uncompleted tasks", + "location": { + "file": "cypress/e2e/todo-one.cy.js" + }, + "attempt": { + "durationInNanoseconds": 309000000, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "example to-do app one with a checked task more nesting can filter for completed tasks", + "location": { + "file": "cypress/e2e/todo-one.cy.js" + }, + "attempt": { + "durationInNanoseconds": 324000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "example to-do app one with a checked task more nesting can delete all completed tasks", + "location": { + "file": "cypress/e2e/todo-one.cy.js" + }, + "attempt": { + "durationInNanoseconds": 362000000, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "example to-do app one with something else can filter for uncompleted tasks", + "location": { + "file": "cypress/e2e/todo-one.cy.js" + }, + "attempt": { + "durationInNanoseconds": 318000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "example to-do app one with something else can filter for completed tasks", + "location": { + "file": "cypress/e2e/todo-one.cy.js" + }, + "attempt": { + "durationInNanoseconds": 318000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "example to-do app one with something else can delete all completed tasks", + "location": { + "file": "cypress/e2e/todo-one.cy.js" + }, + "attempt": { + "durationInNanoseconds": 317000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "example to-do app two displays two todo items by default", + "location": { + "file": "cypress/e2e/todo-two.cy.js" + }, + "attempt": { + "durationInNanoseconds": 270000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "example to-do app two can add new todo items", + "location": { + "file": "cypress/e2e/todo-two.cy.js" + }, + "attempt": { + "durationInNanoseconds": 374000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "example to-do app two can check off an item as completed", + "location": { + "file": "cypress/e2e/todo-two.cy.js" + }, + "attempt": { + "durationInNanoseconds": 165000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "example to-do app two with a checked task can filter for uncompleted tasks", + "location": { + "file": "cypress/e2e/todo-two.cy.js" + }, + "attempt": { + "durationInNanoseconds": 273000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "example to-do app two with a checked task can filter for completed tasks", + "location": { + "file": "cypress/e2e/todo-two.cy.js" + }, + "attempt": { + "durationInNanoseconds": 318000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "example to-do app two with a checked task can delete all completed tasks", + "location": { + "file": "cypress/e2e/todo-two.cy.js" + }, + "attempt": { + "durationInNanoseconds": 258000000, + "status": { + "kind": "successful" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/JavaScriptJestParser Parse parses the sample file b/internal/captain/parsing/.snapshots/JavaScriptJestParser Parse parses the sample file new file mode 100644 index 0000000..d3d96f3 --- /dev/null +++ b/internal/captain/parsing/.snapshots/JavaScriptJestParser Parse parses the sample file @@ -0,0 +1,380 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "JavaScript", + "kind": "Jest" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 18, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 6, + "pended": 3, + "quarantined": 0, + "skipped": 0, + "successful": 6, + "timedOut": 0, + "todo": 3 + }, + "tests": [ + { + "name": "is top-level pending", + "lineage": [ + "is top-level pending" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js" + }, + "attempt": { + "durationInNanoseconds": null, + "status": { + "kind": "todo" + } + } + }, + { + "name": "is top-level skipped", + "lineage": [ + "is top-level skipped" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js" + }, + "attempt": { + "durationInNanoseconds": null, + "status": { + "kind": "pended" + } + } + }, + { + "name": "is top-level passing", + "lineage": [ + "is top-level passing" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js" + }, + "attempt": { + "durationInNanoseconds": 1000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "is top-level failing", + "lineage": [ + "is top-level failing" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js" + }, + "attempt": { + "durationInNanoseconds": 2000000, + "status": { + "kind": "failed", + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js:12:16)", + "at Promise.then.completed (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:333:28)", + "at new Promise (\u003canonymous\u003e)", + "at callAsyncCircusFn (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:259:10)", + "at _callCircusTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:277:40)", + "at processTicksAndRejections (node:internal/process/task_queues:96:5)", + "at _runTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:209:3)", + "at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:97:9)", + "at run (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:31:3)", + "at runAndTransformResultsToJestFormat (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:136:21)", + "at jestAdapter (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:92:19)", + "at runTestInternal (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:407:16)", + "at runTest (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:491:34)", + "at Object.worker (/home/runner/work/captain/captain/node_modules/jest-runner/build/testWorker.js:133:12)" + ] + } + } + }, + { + "name": "is asynchronously top-level passing", + "lineage": [ + "is asynchronously top-level passing" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js" + }, + "attempt": { + "durationInNanoseconds": 653000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "is asynchronously top-level failing", + "lineage": [ + "is asynchronously top-level failing" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js" + }, + "attempt": { + "durationInNanoseconds": 651000000, + "status": { + "kind": "failed", + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js:22:16)" + ] + } + } + }, + { + "name": "even \u003e more nesting \u003e is pending", + "lineage": [ + "even", + "more nesting", + "is pending" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js" + }, + "attempt": { + "durationInNanoseconds": null, + "status": { + "kind": "todo" + } + } + }, + { + "name": "even \u003e more nesting \u003e is skipped", + "lineage": [ + "even", + "more nesting", + "is skipped" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js" + }, + "attempt": { + "durationInNanoseconds": null, + "status": { + "kind": "pended" + } + } + }, + { + "name": "even \u003e more nesting \u003e is passing", + "lineage": [ + "even", + "more nesting", + "is passing" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js" + }, + "attempt": { + "durationInNanoseconds": 1000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "even \u003e more nesting \u003e is failing", + "lineage": [ + "even", + "more nesting", + "is failing" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js" + }, + "attempt": { + "durationInNanoseconds": 1000000, + "status": { + "kind": "failed", + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js:14:20)", + "at Promise.then.completed (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:333:28)", + "at new Promise (\u003canonymous\u003e)", + "at callAsyncCircusFn (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:259:10)", + "at _callCircusTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:277:40)", + "at processTicksAndRejections (node:internal/process/task_queues:96:5)", + "at _runTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:209:3)", + "at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:97:9)", + "at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:91:9)", + "at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:91:9)", + "at run (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:31:3)", + "at runAndTransformResultsToJestFormat (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:136:21)", + "at jestAdapter (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:92:19)", + "at runTestInternal (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:407:16)", + "at runTest (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:491:34)", + "at Object.worker (/home/runner/work/captain/captain/node_modules/jest-runner/build/testWorker.js:133:12)" + ] + } + } + }, + { + "name": "even \u003e more nesting \u003e is asynchronously passing", + "lineage": [ + "even", + "more nesting", + "is asynchronously passing" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js" + }, + "attempt": { + "durationInNanoseconds": 654000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "even \u003e more nesting \u003e is asynchronously failing", + "lineage": [ + "even", + "more nesting", + "is asynchronously failing" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js" + }, + "attempt": { + "durationInNanoseconds": 651000000, + "status": { + "kind": "failed", + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js:24:20)" + ] + } + } + }, + { + "name": "one level of nesting \u003e is pending", + "lineage": [ + "one level of nesting", + "is pending" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js" + }, + "attempt": { + "durationInNanoseconds": null, + "status": { + "kind": "todo" + } + } + }, + { + "name": "one level of nesting \u003e is skipped", + "lineage": [ + "one level of nesting", + "is skipped" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js" + }, + "attempt": { + "durationInNanoseconds": null, + "status": { + "kind": "pended" + } + } + }, + { + "name": "one level of nesting \u003e is passing", + "lineage": [ + "one level of nesting", + "is passing" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js" + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "successful" + } + } + }, + { + "name": "one level of nesting \u003e is failing", + "lineage": [ + "one level of nesting", + "is failing" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js" + }, + "attempt": { + "durationInNanoseconds": 1000000, + "status": { + "kind": "failed", + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js:13:18)", + "at Promise.then.completed (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:333:28)", + "at new Promise (\u003canonymous\u003e)", + "at callAsyncCircusFn (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:259:10)", + "at _callCircusTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:277:40)", + "at processTicksAndRejections (node:internal/process/task_queues:96:5)", + "at _runTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:209:3)", + "at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:97:9)", + "at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:91:9)", + "at run (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:31:3)", + "at runAndTransformResultsToJestFormat (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:136:21)", + "at jestAdapter (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:92:19)", + "at runTestInternal (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:407:16)", + "at runTest (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:491:34)", + "at Object.worker (/home/runner/work/captain/captain/node_modules/jest-runner/build/testWorker.js:133:12)" + ] + } + } + }, + { + "name": "one level of nesting \u003e is asynchronously passing", + "lineage": [ + "one level of nesting", + "is asynchronously passing" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js" + }, + "attempt": { + "durationInNanoseconds": 653000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "one level of nesting \u003e is asynchronously failing", + "lineage": [ + "one level of nesting", + "is asynchronously failing" + ], + "location": { + "file": "/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js" + }, + "attempt": { + "durationInNanoseconds": 652000000, + "status": { + "kind": "failed", + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js:23:18)" + ] + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/JavaScriptKarmaParser Parse parses the sample file b/internal/captain/parsing/.snapshots/JavaScriptKarmaParser Parse parses the sample file new file mode 100644 index 0000000..e64d405 --- /dev/null +++ b/internal/captain/parsing/.snapshots/JavaScriptKarmaParser Parse parses the sample file @@ -0,0 +1,113 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "JavaScript", + "kind": "Karma" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 4, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 1, + "pended": 0, + "quarantined": 0, + "skipped": 1, + "successful": 2, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "id": "spec3", + "name": "Some test context is a contextual passing test", + "scope": "Chrome macOS", + "lineage": [ + "Some test", + "context", + "is a contextual passing test" + ], + "attempt": { + "durationInNanoseconds": 1000000, + "meta": { + "browserFullName": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36", + "browserId": "87843490", + "browserName": "Chrome macOS" + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "spec0", + "name": "Some test is a passing test", + "scope": "Chrome macOS", + "lineage": [ + "Some test", + "is a passing test" + ], + "attempt": { + "durationInNanoseconds": 1000000, + "meta": { + "browserFullName": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36", + "browserId": "87843490", + "browserName": "Chrome macOS" + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "spec1", + "name": "Some test is a skipped test", + "scope": "Chrome macOS", + "lineage": [ + "Some test", + "is a skipped test" + ], + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "browserFullName": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36", + "browserId": "87843490", + "browserName": "Chrome macOS" + }, + "status": { + "kind": "skipped" + } + } + }, + { + "id": "spec2", + "name": "Some test is a failing test", + "scope": "Chrome macOS", + "lineage": [ + "Some test", + "is a failing test" + ], + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "browserFullName": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36", + "browserId": "87843490", + "browserName": "Chrome macOS" + }, + "status": { + "kind": "failed", + "message": "Expected true to be false.", + "backtrace": [ + "at \u003cJasmine\u003e", + "at UserContext.\u003canonymous\u003e (http://localhost:9876/base/tests/some-test.js?0928787523d1ecf8fe8e5c6b04ef9abb48348e11:19:18)", + "at \u003cJasmine\u003e" + ] + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/JavaScriptKarmaParser Parse puts a generic other error in place when there are no errors but the suite failed b/internal/captain/parsing/.snapshots/JavaScriptKarmaParser Parse puts a generic other error in place when there are no errors but the suite failed new file mode 100644 index 0000000..0fb7559 --- /dev/null +++ b/internal/captain/parsing/.snapshots/JavaScriptKarmaParser Parse puts a generic other error in place when there are no errors but the suite failed @@ -0,0 +1,52 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "JavaScript", + "kind": "Karma" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 1, + "flaky": 0, + "otherErrors": 1, + "retries": 0, + "canceled": 0, + "failed": 0, + "pended": 0, + "quarantined": 0, + "skipped": 0, + "successful": 1, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "id": "spec3", + "name": "Some test context is a contextual passing test", + "scope": "Mozilla/5.0 ", + "lineage": [ + "Some test", + "context", + "is a contextual passing test" + ], + "attempt": { + "durationInNanoseconds": 1000000, + "meta": { + "browserFullName": "Mozilla/5.0", + "browserId": "87843490", + "browserName": "Mozilla/5.0 " + }, + "status": { + "kind": "successful" + } + } + } + ], + "otherErrors": [ + { + "message": "An unknown error occurred" + } + ] +} diff --git a/internal/captain/parsing/.snapshots/JavaScriptMochaParser Parse parses the sample file b/internal/captain/parsing/.snapshots/JavaScriptMochaParser Parse parses the sample file new file mode 100644 index 0000000..891a7fa --- /dev/null +++ b/internal/captain/parsing/.snapshots/JavaScriptMochaParser Parse parses the sample file @@ -0,0 +1,272 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "JavaScript", + "kind": "Mocha" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 10, + "flaky": 1, + "otherErrors": 0, + "retries": 2, + "canceled": 0, + "failed": 3, + "pended": 1, + "quarantined": 0, + "skipped": 0, + "successful": 6, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "is an async test", + "lineage": [ + "is an async test" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/async.js" + }, + "attempt": { + "durationInNanoseconds": 570000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "is a passing retried test", + "lineage": [ + "is a passing retried test" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/retries.js" + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "successful" + } + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed" + } + }, + { + "durationInNanoseconds": null, + "status": { + "kind": "failed" + } + } + ] + }, + { + "name": "is a top-level sync passing test", + "lineage": [ + "is a top-level sync passing test" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js" + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "successful" + } + } + }, + { + "name": "nesting is nested one level", + "lineage": [ + "nesting", + "is nested one level" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js" + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "successful" + } + } + }, + { + "name": "nesting more is nested one level", + "lineage": [ + "nesting more", + "is nested one level" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js" + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "successful" + } + } + }, + { + "name": "nesting more is nested two levels", + "lineage": [ + "nesting more", + "is nested two levels" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js" + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "successful" + } + } + }, + { + "name": "is a top-level pended test", + "lineage": [ + "is a top-level pended test" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js" + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "pended" + } + } + }, + { + "name": "is a failing retried test", + "lineage": [ + "is a failing retried test" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/retries.js" + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "failed", + "message": "true == false", + "exception": "AssertionError", + "backtrace": [ + "at Context.\u003canonymous\u003e (test/retries.js:12:10)", + "at processImmediate (node:internal/timers:466:21)" + ] + } + }, + "pastAttempts": [ + { + "durationInNanoseconds": null, + "status": { + "kind": "failed" + } + }, + { + "durationInNanoseconds": null, + "status": { + "kind": "failed" + } + }, + { + "durationInNanoseconds": null, + "status": { + "kind": "failed" + } + }, + { + "durationInNanoseconds": null, + "status": { + "kind": "failed" + } + }, + { + "durationInNanoseconds": null, + "status": { + "kind": "failed" + } + }, + { + "durationInNanoseconds": null, + "status": { + "kind": "failed" + } + }, + { + "durationInNanoseconds": null, + "status": { + "kind": "failed" + } + }, + { + "durationInNanoseconds": null, + "status": { + "kind": "failed" + } + }, + { + "durationInNanoseconds": null, + "status": { + "kind": "failed" + } + }, + { + "durationInNanoseconds": null, + "status": { + "kind": "failed" + } + } + ] + }, + { + "name": "is a top-level sync failing test", + "lineage": [ + "is a top-level sync failing test" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js" + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "failed", + "message": "true == false", + "exception": "AssertionError", + "backtrace": [ + "at Context.\u003canonymous\u003e (test/top-level-sync.js:8:10)", + "at processImmediate (node:internal/timers:466:21)" + ] + } + } + }, + { + "name": "is a top-level sync exceptional test", + "lineage": [ + "is a top-level sync exceptional test" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js" + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "failed", + "message": "Uh oh", + "backtrace": [ + "at Context.\u003canonymous\u003e (test/top-level-sync.js:12:9)", + "at processImmediate (node:internal/timers:466:21)" + ] + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/JavaScriptPlaywrightParser Parse parses the sample file b/internal/captain/parsing/.snapshots/JavaScriptPlaywrightParser Parse parses the sample file new file mode 100644 index 0000000..0fe213d --- /dev/null +++ b/internal/captain/parsing/.snapshots/JavaScriptPlaywrightParser Parse parses the sample file @@ -0,0 +1,2650 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "JavaScript", + "kind": "Playwright" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 64, + "flaky": 8, + "otherErrors": 0, + "retries": 20, + "canceled": 0, + "failed": 12, + "pended": 0, + "quarantined": 0, + "skipped": 8, + "successful": 40, + "timedOut": 4, + "todo": 0 + }, + "tests": [ + { + "name": "New Todo should allow me to add todo items", + "scope": "chromium", + "lineage": [ + "New Todo", + "should allow me to add todo items" + ], + "location": { + "file": "demo-todo-app.spec.ts", + "line": 14, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 778000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:02.753Z" + } + }, + { + "name": "New Todo should allow me to add todo items", + "scope": "firefox", + "lineage": [ + "New Todo", + "should allow me to add todo items" + ], + "location": { + "file": "demo-todo-app.spec.ts", + "line": 14, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 928000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:10.426Z" + } + }, + { + "name": "New Todo should clear text input field when an item is added", + "scope": "chromium", + "lineage": [ + "New Todo", + "should clear text input field when an item is added" + ], + "location": { + "file": "demo-todo-app.spec.ts", + "line": 40, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 764000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:02.752Z" + } + }, + { + "name": "New Todo should append new items to the bottom of the list", + "scope": "chromium", + "lineage": [ + "New Todo", + "should append new items to the bottom of the list" + ], + "location": { + "file": "demo-todo-app.spec.ts", + "line": 53, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 806000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:02.756Z" + } + }, + { + "name": "New Todo should clear text input field when an item is added", + "scope": "firefox", + "lineage": [ + "New Todo", + "should clear text input field when an item is added" + ], + "location": { + "file": "demo-todo-app.spec.ts", + "line": 40, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 792000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:10.655Z" + } + }, + { + "name": "New Todo should append new items to the bottom of the list", + "scope": "firefox", + "lineage": [ + "New Todo", + "should append new items to the bottom of the list" + ], + "location": { + "file": "demo-todo-app.spec.ts", + "line": 53, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 1103000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:11.33Z" + } + }, + { + "name": "Mark all as completed should allow me to mark all items as completed", + "scope": "chromium", + "lineage": [ + "Mark all as completed", + "should allow me to mark all items as completed" + ], + "location": { + "file": "demo-todo-app.spec.ts", + "line": 82, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 851000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:02.754Z" + } + }, + { + "name": "Mark all as completed should allow me to clear the complete state of all items", + "scope": "chromium", + "lineage": [ + "Mark all as completed", + "should allow me to clear the complete state of all items" + ], + "location": { + "file": "demo-todo-app.spec.ts", + "line": 91, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 882000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:02.757Z" + } + }, + { + "name": "Mark all as completed complete all checkbox should update state when items are completed / cleared", + "scope": "chromium", + "lineage": [ + "Mark all as completed", + "complete all checkbox should update state when items are completed / cleared" + ], + "location": { + "file": "demo-todo-app.spec.ts", + "line": 101, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 446000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:03.899Z" + } + }, + { + "name": "Mark all as completed should allow me to mark all items as completed", + "scope": "firefox", + "lineage": [ + "Mark all as completed", + "should allow me to mark all items as completed" + ], + "location": { + "file": "demo-todo-app.spec.ts", + "line": 82, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 1278000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:11.845Z" + } + }, + { + "name": "Mark all as completed should allow me to clear the complete state of all items", + "scope": "firefox", + "lineage": [ + "Mark all as completed", + "should allow me to clear the complete state of all items" + ], + "location": { + "file": "demo-todo-app.spec.ts", + "line": 91, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 803000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:11.711Z" + } + }, + { + "name": "Mark all as completed complete all checkbox should update state when items are completed / cleared", + "scope": "firefox", + "lineage": [ + "Mark all as completed", + "complete all checkbox should update state when items are completed / cleared" + ], + "location": { + "file": "demo-todo-app.spec.ts", + "line": 101, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 834000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:11.717Z" + } + }, + { + "name": "homepage has title and links to intro page", + "scope": "chromium", + "lineage": [ + "homepage has title and links to intro page" + ], + "location": { + "file": "example.spec.ts", + "line": 3, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 607000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "some stdout\nhappened in here\n", + "startedAt": "2022-12-15T20:31:03.915Z" + } + }, + { + "name": "skipped test", + "scope": "chromium", + "lineage": [ + "skipped test" + ], + "location": { + "file": "example.spec.ts", + "line": 25, + "column": 6 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "annotations": [ + { + "type": "skip" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:03.944Z" + } + }, + { + "name": "fixme test", + "scope": "chromium", + "lineage": [ + "fixme test" + ], + "location": { + "file": "example.spec.ts", + "line": 31, + "column": 6 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "annotations": [ + { + "type": "fixme" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:03.945Z" + } + }, + { + "name": "failing test", + "scope": "chromium", + "lineage": [ + "failing test" + ], + "location": { + "file": "example.spec.ts", + "line": 37, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 487000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:05.655Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 352000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:03.945Z" + }, + { + "durationInNanoseconds": 711000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-failing-test-chromium-retry1/trace.zip" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:04.561Z" + } + ] + }, + { + "name": "throw error test", + "scope": "chromium", + "lineage": [ + "throw error test" + ], + "location": { + "file": "example.spec.ts", + "line": 42, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 496000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "uh oh", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:05.655Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 395000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "uh oh", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:03.988Z" + }, + { + "durationInNanoseconds": 622000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-throw-error-test-chromium-retry1/trace.zip" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "uh oh", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:04.638Z" + } + ] + }, + { + "name": "throw string", + "scope": "chromium", + "lineage": [ + "throw string" + ], + "location": { + "file": "example.spec.ts", + "line": 47, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 436000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:05.611Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 353000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:04.02Z" + }, + { + "durationInNanoseconds": 618000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-throw-string-chromium-retry1/trace.zip" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:04.629Z" + } + ] + }, + { + "name": "passing test", + "scope": "chromium", + "lineage": [ + "passing test" + ], + "location": { + "file": "example.spec.ts", + "line": 52, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 277000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:04.351Z" + } + }, + { + "name": "failing w/ fail annotation", + "scope": "chromium", + "lineage": [ + "failing w/ fail annotation" + ], + "location": { + "file": "example.spec.ts", + "line": 71, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 2780000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "some stderr\nhappened in here\n", + "stdout": "", + "startedAt": "2022-12-15T20:31:04.841Z" + } + }, + { + "name": "passing w/ fail annotation", + "scope": "chromium", + "lineage": [ + "passing w/ fail annotation" + ], + "location": { + "file": "example.spec.ts", + "line": 82, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 415000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "Expected the test to fail, but it did not" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:06.587Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 383000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:04.964Z" + }, + { + "durationInNanoseconds": 573000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-passing-w-fail-annotation-chromium-retry1/trace.zip" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:05.598Z" + } + ] + }, + { + "name": "fails then passes", + "scope": "chromium", + "lineage": [ + "fails then passes" + ], + "location": { + "file": "example.spec.ts", + "line": 90, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 158000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:07.441Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 145000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:91:26" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:06.407Z" + }, + { + "durationInNanoseconds": 138000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-fails-then-passes-chromium-retry1/trace.zip" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:91:26" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:06.966Z" + } + ] + }, + { + "name": "times out", + "scope": "chromium", + "lineage": [ + "times out" + ], + "location": { + "file": "example.spec.ts", + "line": 94, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 1001000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "timedOut", + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:09.293Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 1001000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "timedOut", + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:06.525Z" + }, + { + "durationInNanoseconds": 1000000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-times-out-chromium-retry1/trace.zip" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "timedOut", + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:07.92Z" + } + ] + }, + { + "name": "slow", + "scope": "chromium", + "lineage": [ + "slow" + ], + "location": { + "file": "example.spec.ts", + "line": 99, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 1658000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:06.555Z" + } + }, + { + "name": "homepage has title and links to intro page", + "scope": "firefox", + "lineage": [ + "homepage has title and links to intro page" + ], + "location": { + "file": "example.spec.ts", + "line": 3, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 598000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "some stdout\nhappened in here\n", + "startedAt": "2022-12-15T20:31:12.518Z" + } + }, + { + "name": "skipped test", + "scope": "firefox", + "lineage": [ + "skipped test" + ], + "location": { + "file": "example.spec.ts", + "line": 25, + "column": 6 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "annotations": [ + { + "type": "skip" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "skipped" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:12.554Z" + } + }, + { + "name": "fixme test", + "scope": "firefox", + "lineage": [ + "fixme test" + ], + "location": { + "file": "example.spec.ts", + "line": 31, + "column": 6 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "annotations": [ + { + "type": "fixme" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "skipped" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:12.555Z" + } + }, + { + "name": "failing test", + "scope": "firefox", + "lineage": [ + "failing test" + ], + "location": { + "file": "example.spec.ts", + "line": 37, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 1008000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:17.7Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 407000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:12.555Z" + }, + { + "durationInNanoseconds": 1077000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-failing-test-firefox-retry1/trace.zip" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:14.175Z" + } + ] + }, + { + "name": "throw error test", + "scope": "firefox", + "lineage": [ + "throw error test" + ], + "location": { + "file": "example.spec.ts", + "line": 42, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 758000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "uh oh", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:18.483Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 459000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "uh oh", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:12.72Z" + }, + { + "durationInNanoseconds": 873000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-throw-error-test-firefox-retry1/trace.zip" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "uh oh", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:14.894Z" + } + ] + }, + { + "name": "throw string", + "scope": "firefox", + "lineage": [ + "throw string" + ], + "location": { + "file": "example.spec.ts", + "line": 47, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 855000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:17.565Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 392000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:13.117Z" + }, + { + "durationInNanoseconds": 1089000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-throw-string-firefox-retry1/trace.zip" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:14.046Z" + } + ] + }, + { + "name": "passing test", + "scope": "firefox", + "lineage": [ + "passing test" + ], + "location": { + "file": "example.spec.ts", + "line": 52, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 829000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:13.491Z" + } + }, + { + "name": "failing w/ fail annotation", + "scope": "firefox", + "lineage": [ + "failing w/ fail annotation" + ], + "location": { + "file": "example.spec.ts", + "line": 71, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 2929000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "some stderr\nhappened in here\n", + "stdout": "", + "startedAt": "2022-12-15T20:31:14.227Z" + } + }, + { + "name": "passing w/ fail annotation", + "scope": "firefox", + "lineage": [ + "passing w/ fail annotation" + ], + "location": { + "file": "example.spec.ts", + "line": 82, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 824000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "Expected the test to fail, but it did not" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:20.474Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 498000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:14.607Z" + }, + { + "durationInNanoseconds": 1024000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-passing-w-fail-annotation-firefox-retry1/trace.zip" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:17.007Z" + } + ] + }, + { + "name": "fails then passes", + "scope": "firefox", + "lineage": [ + "fails then passes" + ], + "location": { + "file": "example.spec.ts", + "line": 90, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 464000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:21.361Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 113000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:91:26" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:17.156Z" + }, + { + "durationInNanoseconds": 568000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-fails-then-passes-firefox-retry1/trace.zip" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:91:26" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:17.784Z" + } + ] + }, + { + "name": "times out", + "scope": "firefox", + "lineage": [ + "times out" + ], + "location": { + "file": "example.spec.ts", + "line": 94, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 1000000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "timedOut", + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:28.119Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 1001000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "timedOut", + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:21.097Z" + }, + { + "durationInNanoseconds": 1000000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-times-out-firefox-retry1/trace.zip" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "timedOut", + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:24.612Z" + } + ] + }, + { + "name": "slow", + "scope": "firefox", + "lineage": [ + "slow" + ], + "location": { + "file": "example.spec.ts", + "line": 99, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 1974000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:21.245Z" + } + }, + { + "name": "first level passing test", + "scope": "chromium", + "lineage": [ + "first level", + "passing test" + ], + "location": { + "file": "example.spec.ts", + "line": 58, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 316000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:04.523Z" + } + }, + { + "name": "first level passing test", + "scope": "firefox", + "lineage": [ + "first level", + "passing test" + ], + "location": { + "file": "example.spec.ts", + "line": 58, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 413000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:13.465Z" + } + }, + { + "name": "first level second level passing test", + "scope": "chromium", + "lineage": [ + "first level", + "second level", + "passing test" + ], + "location": { + "file": "example.spec.ts", + "line": 64, + "column": 5 + }, + "attempt": { + "durationInNanoseconds": 334000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:04.629Z" + } + }, + { + "name": "first level second level passing test", + "scope": "firefox", + "lineage": [ + "first level", + "second level", + "passing test" + ], + "location": { + "file": "example.spec.ts", + "line": 64, + "column": 5 + }, + "attempt": { + "durationInNanoseconds": 346000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:13.88Z" + } + }, + { + "name": "homepage has title and links to intro page", + "scope": "chromium", + "lineage": [ + "homepage has title and links to intro page" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 3, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 573000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "some stdout\nhappened in here\n", + "startedAt": "2022-12-15T20:31:07.335Z" + } + }, + { + "name": "skipped test", + "scope": "chromium", + "lineage": [ + "skipped test" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 25, + "column": 6 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "annotations": [ + { + "type": "skip" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:07.624Z" + } + }, + { + "name": "fixme test", + "scope": "chromium", + "lineage": [ + "fixme test" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 31, + "column": 6 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "annotations": [ + { + "type": "fixme" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:07.625Z" + } + }, + { + "name": "failing test", + "scope": "chromium", + "lineage": [ + "failing test" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 37, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 435000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:09.063Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 310000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:07.626Z" + }, + { + "durationInNanoseconds": 519000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-failing-test-chromium-retry1/trace.zip" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:08.194Z" + } + ] + }, + { + "name": "passing test", + "scope": "chromium", + "lineage": [ + "passing test" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 42, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 344000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:07.695Z" + } + }, + { + "name": "failing w/ fail annotation", + "scope": "chromium", + "lineage": [ + "failing w/ fail annotation" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 61, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 2800000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "some stderr\nhappened in here\n", + "stdout": "", + "startedAt": "2022-12-15T20:31:08.278Z" + } + }, + { + "name": "passing w/ fail annotation", + "scope": "chromium", + "lineage": [ + "passing w/ fail annotation" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 72, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 386000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "Expected the test to fail, but it did not" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:09.692Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 296000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:08.312Z" + }, + { + "durationInNanoseconds": 481000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-passing-w-fail-annotation-chromium-retry1/trace.zip" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:08.86Z" + } + ] + }, + { + "name": "fails then passes", + "scope": "chromium", + "lineage": [ + "fails then passes" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 80, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 152000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:09.134Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 53000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:81:26" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:08.317Z" + }, + { + "durationInNanoseconds": 163000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-fails-then-passes-chromium-retry1/trace.zip" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:81:26" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:08.62Z" + } + ] + }, + { + "name": "times out", + "scope": "chromium", + "lineage": [ + "times out" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 84, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 1000000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "timedOut", + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:12.072Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 1000000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "timedOut", + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:09.386Z" + }, + { + "durationInNanoseconds": 999000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-times-out-chromium-retry1/trace.zip" + } + ], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "timedOut", + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:10.628Z" + } + ] + }, + { + "name": "slow", + "scope": "chromium", + "lineage": [ + "slow" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 89, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 1625000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:09.858Z" + } + }, + { + "name": "homepage has title and links to intro page", + "scope": "firefox", + "lineage": [ + "homepage has title and links to intro page" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 3, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 899000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "some stdout\nhappened in here\n", + "startedAt": "2022-12-15T20:31:22.074Z" + } + }, + { + "name": "skipped test", + "scope": "firefox", + "lineage": [ + "skipped test" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 25, + "column": 6 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "annotations": [ + { + "type": "skip" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "skipped" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:22.115Z" + } + }, + { + "name": "fixme test", + "scope": "firefox", + "lineage": [ + "fixme test" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 31, + "column": 6 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "annotations": [ + { + "type": "fixme" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "skipped" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:22.118Z" + } + }, + { + "name": "failing test", + "scope": "firefox", + "lineage": [ + "failing test" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 37, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 826000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:28.468Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 536000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:22.119Z" + }, + { + "durationInNanoseconds": 878000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-failing-test-firefox-retry1/trace.zip" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:24.887Z" + } + ] + }, + { + "name": "passing test", + "scope": "firefox", + "lineage": [ + "passing test" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 42, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 573000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:23.254Z" + } + }, + { + "name": "failing w/ fail annotation", + "scope": "firefox", + "lineage": [ + "failing w/ fail annotation" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 61, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 2869000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "some stderr\nhappened in here\n", + "stdout": "", + "startedAt": "2022-12-15T20:31:23.828Z" + } + }, + { + "name": "passing w/ fail annotation", + "scope": "firefox", + "lineage": [ + "passing w/ fail annotation" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 72, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 896000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "Expected the test to fail, but it did not" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:28.374Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 386000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:23.947Z" + }, + { + "durationInNanoseconds": 945000000, + "meta": { + "annotations": [ + { + "type": "fail" + } + ], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-passing-w-fail-annotation-firefox-retry1/trace.zip" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:24.843Z" + } + ] + }, + { + "name": "fails then passes", + "scope": "firefox", + "lineage": [ + "fails then passes" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 80, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 380000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:30.93Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 139000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:81:26" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:25.064Z" + }, + { + "durationInNanoseconds": 459000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-fails-then-passes-firefox-retry1/trace.zip" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "failed", + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:81:26" + ] + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:27.484Z" + } + ] + }, + { + "name": "times out", + "scope": "firefox", + "lineage": [ + "times out" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 84, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 1001000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "timedOut", + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:31.758Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 1000000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "timedOut", + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:26.698Z" + }, + { + "durationInNanoseconds": 1000000000, + "meta": { + "annotations": [], + "fileAttachments": [ + { + "name": "trace", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-times-out-firefox-retry1/trace.zip" + } + ], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "timedOut", + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:28.204Z" + } + ] + }, + { + "name": "slow", + "scope": "firefox", + "lineage": [ + "slow" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 89, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 1857000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:31.648Z" + } + }, + { + "name": "first level passing test", + "scope": "chromium", + "lineage": [ + "first level", + "passing test" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 48, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 269000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:08.008Z" + } + }, + { + "name": "first level passing test", + "scope": "firefox", + "lineage": [ + "first level", + "passing test" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 48, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 435000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:23.511Z" + } + }, + { + "name": "first level second level passing test", + "scope": "chromium", + "lineage": [ + "first level", + "second level", + "passing test" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 54, + "column": 5 + }, + "attempt": { + "durationInNanoseconds": 269000000, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:08.041Z" + } + }, + { + "name": "first level second level passing test", + "scope": "firefox", + "lineage": [ + "first level", + "second level", + "passing test" + ], + "location": { + "file": "nested/example.spec.ts", + "line": 54, + "column": 5 + }, + "attempt": { + "durationInNanoseconds": 813000000, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "successful" + }, + "stderr": "", + "stdout": "", + "startedAt": "2022-12-15T20:31:23.975Z" + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/JavaScriptPlaywrightParser Parse parses the sample file with an error in global setup b/internal/captain/parsing/.snapshots/JavaScriptPlaywrightParser Parse parses the sample file with an error in global setup new file mode 100644 index 0000000..ade57a2 --- /dev/null +++ b/internal/captain/parsing/.snapshots/JavaScriptPlaywrightParser Parse parses the sample file with an error in global setup @@ -0,0 +1,385 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "JavaScript", + "kind": "Playwright" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 14, + "flaky": 0, + "otherErrors": 1, + "retries": 0, + "canceled": 0, + "failed": 0, + "pended": 0, + "quarantined": 0, + "skipped": 14, + "successful": 0, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "Auth Flow You can signup and login with GitHub", + "scope": "chromium", + "lineage": [ + "Auth Flow", + "You can signup and login with GitHub" + ], + "location": { + "file": "auth.spec.ts", + "line": 9, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Deep links work across domains when choosing an organization", + "scope": "chromium", + "lineage": [ + "Deep links", + "work across domains when choosing an organization" + ], + "location": { + "file": "deep-link.spec.ts", + "line": 11, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Feedback submission functionality Allows the submission of feedback to the support email address", + "scope": "chromium", + "lineage": [ + "Feedback submission functionality", + "Allows the submission of feedback to the support email address" + ], + "location": { + "file": "feedback.spec.ts", + "line": 8, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Forgot Password Flow You can sign up with email, forget your password, ask to reset it, reset it, and sign in with the new password", + "scope": "chromium", + "lineage": [ + "Forgot Password Flow", + "You can sign up with email, forget your password, ask to reset it, reset it, and sign in with the new password" + ], + "location": { + "file": "forgot-password.spec.ts", + "line": 9, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Login Flow When you are logged out and visit an authenticated path, you are redirected to the authenticated path after login", + "scope": "chromium", + "lineage": [ + "Login Flow", + "When you are logged out and visit an authenticated path, you are redirected to the authenticated path after login" + ], + "location": { + "file": "login.spec.ts", + "line": 9, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Login Flow When you are logged out and visit an authenticated path, you are redirected to the authenticated path after signup", + "scope": "chromium", + "lineage": [ + "Login Flow", + "When you are logged out and visit an authenticated path, you are redirected to the authenticated path after signup" + ], + "location": { + "file": "login.spec.ts", + "line": 32, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Onboarding Flow You can sign up to captain with email, create an organization, invite someone to that organization, and then they can signup with the invite code and automatically be added to the organization.", + "scope": "chromium", + "lineage": [ + "Onboarding Flow", + "You can sign up to captain with email, create an organization, invite someone to that organization, and then they can signup with the invite code and automatically be added to the organization." + ], + "location": { + "file": "onboarding.spec.ts", + "line": 60, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Onboarding Flow You can sign up to captain with email, create an organization, invite someone who already has an account to that organization, and then they can be deeplink logged in and added to the organization.", + "scope": "chromium", + "lineage": [ + "Onboarding Flow", + "You can sign up to captain with email, create an organization, invite someone who already has an account to that organization, and then they can be deeplink logged in and added to the organization." + ], + "location": { + "file": "onboarding.spec.ts", + "line": 76, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Onboarding Flow You can sign up for abq with email and create an organization.", + "scope": "chromium", + "lineage": [ + "Onboarding Flow", + "You can sign up for abq with email and create an organization." + ], + "location": { + "file": "onboarding.spec.ts", + "line": 95, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "One Click Onboarding Flow You can sign up with Github through the one click flow", + "scope": "chromium", + "lineage": [ + "One Click Onboarding Flow", + "You can sign up with Github through the one click flow" + ], + "location": { + "file": "one-click-onboarding.spec.ts", + "line": 6, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "One Click Onboarding Flow You can see the unsupported message and form for non-Github selections in the one click flow", + "scope": "chromium", + "lineage": [ + "One Click Onboarding Flow", + "You can see the unsupported message and form for non-Github selections in the one click flow" + ], + "location": { + "file": "one-click-onboarding.spec.ts", + "line": 17, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "One Click Onboarding Flow You can see the unsupported message and form for not-listed selections in the one click flow", + "scope": "chromium", + "lineage": [ + "One Click Onboarding Flow", + "You can see the unsupported message and form for not-listed selections in the one click flow" + ], + "location": { + "file": "one-click-onboarding.spec.ts", + "line": 40, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Telemetry functionality Telemetry is enabled by default but can be opted out of", + "scope": "chromium", + "lineage": [ + "Telemetry functionality", + "Telemetry is enabled by default but can be opted out of" + ], + "location": { + "file": "telemetry.spec.ts", + "line": 7, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Automatic time zone detection Initially sets user's time zones to the browser's and lets them change it", + "scope": "chromium", + "lineage": [ + "Automatic time zone detection", + "Initially sets user's time zones to the browser's and lets them change it" + ], + "location": { + "file": "time-zone.spec.ts", + "line": 8, + "column": 7 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + } + ], + "otherErrors": [ + { + "backtrace": [ + "at JSON.parse (\u003canonymous\u003e)", + "at APIResponse.json (/__w/captain/captain/node_modules/playwright-core/lib/client/fetch.js:238:17)", + "at runNextTicks (node:internal/process/task_queues:61:5)", + "at processImmediate (node:internal/timers:437:9)", + "at _test.expect.poll.timeout (/__w/captain/captain/playwright/global-setup.ts:17:26)", + "at /__w/captain/captain/node_modules/@playwright/test/lib/expect.js:179:19", + "at TimeoutRunner.run (/__w/captain/captain/node_modules/playwright-core/lib/utils/timeoutRunner.js:46:14)", + "at raceAgainstTimeout (/__w/captain/captain/node_modules/playwright-core/lib/utils/timeoutRunner.js:90:15)", + "at pollAgainstTimeout (/__w/captain/captain/node_modules/playwright-core/lib/utils/timeoutRunner.js:110:38)", + "at pollMatcher (/__w/captain/captain/node_modules/@playwright/test/lib/expect.js:178:18)", + "at globalSetup (/__w/captain/captain/playwright/global-setup.ts:5:3)", + "at /__w/captain/captain/node_modules/@playwright/test/lib/runner.js:484:11", + "at Runner._runAndReportError (/__w/captain/captain/node_modules/@playwright/test/lib/runner.js:501:7)", + "at Runner._performGlobalSetup (/__w/captain/captain/node_modules/@playwright/test/lib/runner.js:470:5)", + "at Runner._run (/__w/captain/captain/node_modules/@playwright/test/lib/runner.js:380:28)\"\n\nCall Log:\n- Timeout 60000ms exceeded while waiting on the predicate", + "at globalSetup (/__w/captain/captain/playwright/global-setup.ts:5:3)" + ], + "message": "expect(received).toHaveProperty(path)\n\nExpected path: \"pollingVal\"\nReceived path: []\n\nReceived value: \"Error while fetching Rails Config:\nSyntaxError: Unexpected token H in JSON at position 0\n at JSON.parse (\u003canonymous\u003e)\n at APIResponse.json (/__w/captain/captain/node_modules/playwright-core/lib/client/fetch.js:238:17)\n at runNextTicks (node:internal/process/task_queues:61:5)\n at processImmediate (node:internal/timers:437:9)\n at _test.expect.poll.timeout (/__w/captain/captain/playwright/global-setup.ts:17:26)\n at /__w/captain/captain/node_modules/@playwright/test/lib/expect.js:179:19\n at TimeoutRunner.run (/__w/captain/captain/node_modules/playwright-core/lib/utils/timeoutRunner.js:46:14)\n at raceAgainstTimeout (/__w/captain/captain/node_modules/playwright-core/lib/utils/timeoutRunner.js:90:15)\n at pollAgainstTimeout (/__w/captain/captain/node_modules/playwright-core/lib/utils/timeoutRunner.js:110:38)\n at pollMatcher (/__w/captain/captain/node_modules/@playwright/test/lib/expect.js:178:18)\n at globalSetup (/__w/captain/captain/playwright/global-setup.ts:5:3)\n at /__w/captain/captain/node_modules/@playwright/test/lib/runner.js:484:11\n at Runner._runAndReportError (/__w/captain/captain/node_modules/@playwright/test/lib/runner.js:501:7)\n at Runner._performGlobalSetup (/__w/captain/captain/node_modules/@playwright/test/lib/runner.js:470:5)\n at Runner._run (/__w/captain/captain/node_modules/@playwright/test/lib/runner.js:380:28)\"\n\nCall Log:\n- Timeout 60000ms exceeded while waiting on the predicate" + } + ] +} diff --git a/internal/captain/parsing/.snapshots/JavaScriptPlaywrightParser Parse parses the sample file with other errors b/internal/captain/parsing/.snapshots/JavaScriptPlaywrightParser Parse parses the sample file with other errors new file mode 100644 index 0000000..352a89c --- /dev/null +++ b/internal/captain/parsing/.snapshots/JavaScriptPlaywrightParser Parse parses the sample file with other errors @@ -0,0 +1,195 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "JavaScript", + "kind": "Playwright" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 7, + "flaky": 0, + "otherErrors": 1, + "retries": 0, + "canceled": 0, + "failed": 0, + "pended": 0, + "quarantined": 0, + "skipped": 7, + "successful": 0, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "homepage has title and links to intro page", + "scope": "chromium", + "lineage": [ + "homepage has title and links to intro page" + ], + "location": { + "file": "error.spec.ts", + "line": 3, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "chromium", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "homepage has title and links to intro page", + "scope": "firefox", + "lineage": [ + "homepage has title and links to intro page" + ], + "location": { + "file": "error.spec.ts", + "line": 3, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "firefox", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "homepage has title and links to intro page", + "scope": "webkit", + "lineage": [ + "homepage has title and links to intro page" + ], + "location": { + "file": "error.spec.ts", + "line": 3, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "webkit", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "homepage has title and links to intro page", + "scope": "Mobile Chrome", + "lineage": [ + "homepage has title and links to intro page" + ], + "location": { + "file": "error.spec.ts", + "line": 3, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "Mobile Chrome", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "homepage has title and links to intro page", + "scope": "Mobile Safari", + "lineage": [ + "homepage has title and links to intro page" + ], + "location": { + "file": "error.spec.ts", + "line": 3, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "Mobile Safari", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "homepage has title and links to intro page", + "scope": "Microsoft Edge", + "lineage": [ + "homepage has title and links to intro page" + ], + "location": { + "file": "error.spec.ts", + "line": 3, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "Microsoft Edge", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "homepage has title and links to intro page", + "scope": "Google Chrome", + "lineage": [ + "homepage has title and links to intro page" + ], + "location": { + "file": "error.spec.ts", + "line": 3, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": null, + "meta": { + "annotations": [], + "project": "Google Chrome", + "tags": [] + }, + "status": { + "kind": "skipped" + } + } + } + ], + "otherErrors": [ + { + "backtrace": [ + "at Object.\u003canonymous\u003e (/Users/kylekthompson/src/captain-examples/playwright/tests/error.spec.ts:22:7)" + ], + "message": "it broke" + } + ] +} diff --git a/internal/captain/parsing/.snapshots/JavaScriptVitestParser Parse parses the sample file b/internal/captain/parsing/.snapshots/JavaScriptVitestParser Parse parses the sample file new file mode 100644 index 0000000..0452c96 --- /dev/null +++ b/internal/captain/parsing/.snapshots/JavaScriptVitestParser Parse parses the sample file @@ -0,0 +1,232 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "JavaScript", + "kind": "Vitest" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 10, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 2, + "pended": 0, + "quarantined": 0, + "skipped": 2, + "successful": 5, + "timedOut": 0, + "todo": 1 + }, + "tests": [ + { + "name": "adds 1 + 2 to equal 3", + "lineage": [ + "adds 1 + 2 to equal 3" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/vitest/sum.test.js", + "line": 4, + "column": 1 + }, + "attempt": { + "durationInNanoseconds": 1000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "it is pended", + "lineage": [ + "it is pended" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/vitest/sum.test.js", + "line": 8, + "column": 6 + }, + "attempt": { + "durationInNanoseconds": null, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "it is not written yet", + "lineage": [ + "it is not written yet" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/vitest/sum.test.js", + "line": 12, + "column": 6 + }, + "attempt": { + "durationInNanoseconds": null, + "status": { + "kind": "todo" + } + } + }, + { + "name": "first level of nesting \u003e it passes", + "lineage": [ + "first level of nesting", + "it passes" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/vitest/sum.test.js", + "line": 15, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "successful" + } + } + }, + { + "name": "first level of nesting \u003e it fails", + "lineage": [ + "first level of nesting", + "it fails" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/vitest/sum.test.js", + "line": 19, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 2000000, + "status": { + "kind": "failed", + "message": "AssertionError: expected 1 to be 2 // Object.is equality", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/vitest/sum.test.js:20:15", + "at file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:146:14", + "at file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:61:7", + "at runTest (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:960:17)", + "at processTicksAndRejections (node:internal/process/task_queues:95:5)", + "at runSuite (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1116:15)", + "at runSuite (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1116:15)", + "at runFiles (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1173:5)", + "at startTests (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1182:3)", + "at file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/vitest/dist/chunks/runBaseTests.CyvqmuC9.js:130:11" + ] + } + } + }, + { + "name": "first level of nesting \u003e it is slow", + "lineage": [ + "first level of nesting", + "it is slow" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/vitest/sum.test.js", + "line": 23, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": 5002000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "first level of nesting \u003e second level of nesting \u003e it passes", + "lineage": [ + "first level of nesting", + "second level of nesting", + "it passes" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/vitest/sum.test.js", + "line": 29, + "column": 5 + }, + "attempt": { + "durationInNanoseconds": 1000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "first level of nesting \u003e second level of nesting \u003e it fails", + "lineage": [ + "first level of nesting", + "second level of nesting", + "it fails" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/vitest/sum.test.js", + "line": 33, + "column": 5 + }, + "attempt": { + "durationInNanoseconds": 2000000, + "status": { + "kind": "failed", + "message": "AssertionError: expected 1 to be 2 // Object.is equality", + "backtrace": [ + "at /Users/kylekthompson/src/captain-examples/vitest/sum.test.js:34:17", + "at file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:146:14", + "at file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:61:7", + "at runTest (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:960:17)", + "at runNextTicks (node:internal/process/task_queues:60:5)", + "at listOnTimeout (node:internal/timers:538:9)", + "at processTimers (node:internal/timers:512:7)", + "at runSuite (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1116:15)", + "at runSuite (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1116:15)", + "at runSuite (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1116:15)" + ] + } + } + }, + { + "name": "first level of nesting \u003e second level of nesting \u003e it is slow", + "lineage": [ + "first level of nesting", + "second level of nesting", + "it is slow" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/vitest/sum.test.js", + "line": 37, + "column": 5 + }, + "attempt": { + "durationInNanoseconds": 5001000000, + "status": { + "kind": "successful" + } + } + }, + { + "name": "skipped whole nested describe \u003e there's a test in here though", + "lineage": [ + "skipped whole nested describe", + "there's a test in here though" + ], + "location": { + "file": "/Users/kylekthompson/src/captain-examples/vitest/sum.test.js", + "line": 45, + "column": 3 + }, + "attempt": { + "durationInNanoseconds": null, + "status": { + "kind": "skipped" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/PHPUnitParser Parse parses the sample file b/internal/captain/parsing/.snapshots/PHPUnitParser Parse parses the sample file new file mode 100644 index 0000000..e1dc1c8 --- /dev/null +++ b/internal/captain/parsing/.snapshots/PHPUnitParser Parse parses the sample file @@ -0,0 +1,359 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "PHP", + "kind": "PHPUnit" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 14, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 5, + "pended": 0, + "quarantined": 0, + "skipped": 4, + "successful": 5, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "Tests\\Unit\\ExampleTest::test_that_true_is_true", + "lineage": [ + "Tests\\Unit\\ExampleTest", + "test_that_true_is_true" + ], + "location": { + "file": "/var/www/html/tests/Unit/ExampleTest.php", + "line": 16 + }, + "attempt": { + "durationInNanoseconds": 13692000, + "meta": { + "class": "Tests\\Unit\\ExampleTest", + "name": "test_that_true_is_true", + "risky": false + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "Tests\\Unit\\ExampleTest::test_that_true_is_false", + "lineage": [ + "Tests\\Unit\\ExampleTest", + "test_that_true_is_false" + ], + "location": { + "file": "/var/www/html/tests/Unit/ExampleTest.php", + "line": 21 + }, + "attempt": { + "durationInNanoseconds": 2771000, + "meta": { + "class": "Tests\\Unit\\ExampleTest", + "name": "test_that_true_is_false", + "risky": false + }, + "status": { + "kind": "failed", + "message": "Failed asserting that true is false.", + "exception": "PHPUnit\\Framework\\ExpectationFailedException", + "backtrace": [ + "/var/www/html/tests/Unit/ExampleTest.php:23" + ] + } + } + }, + { + "name": "Tests\\Unit\\ExampleTest::test_that_is_incomplete_and_failing", + "lineage": [ + "Tests\\Unit\\ExampleTest", + "test_that_is_incomplete_and_failing" + ], + "location": { + "file": "/var/www/html/tests/Unit/ExampleTest.php", + "line": 26 + }, + "attempt": { + "durationInNanoseconds": 1486000, + "meta": { + "class": "Tests\\Unit\\ExampleTest", + "name": "test_that_is_incomplete_and_failing", + "risky": false + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Tests\\Unit\\ExampleTest::test_that_is_incomplete_and_passing", + "lineage": [ + "Tests\\Unit\\ExampleTest", + "test_that_is_incomplete_and_passing" + ], + "location": { + "file": "/var/www/html/tests/Unit/ExampleTest.php", + "line": 32 + }, + "attempt": { + "durationInNanoseconds": 35000, + "meta": { + "class": "Tests\\Unit\\ExampleTest", + "name": "test_that_is_incomplete_and_passing", + "risky": false + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Tests\\Unit\\ExampleTest::test_that_is_skipped_and_failing", + "lineage": [ + "Tests\\Unit\\ExampleTest", + "test_that_is_skipped_and_failing" + ], + "location": { + "file": "/var/www/html/tests/Unit/ExampleTest.php", + "line": 38 + }, + "attempt": { + "durationInNanoseconds": 2180000, + "meta": { + "class": "Tests\\Unit\\ExampleTest", + "name": "test_that_is_skipped_and_failing", + "risky": false + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Tests\\Unit\\ExampleTest::test_that_is_skipped_and_passing", + "lineage": [ + "Tests\\Unit\\ExampleTest", + "test_that_is_skipped_and_passing" + ], + "location": { + "file": "/var/www/html/tests/Unit/ExampleTest.php", + "line": 44 + }, + "attempt": { + "durationInNanoseconds": 63000, + "meta": { + "class": "Tests\\Unit\\ExampleTest", + "name": "test_that_is_skipped_and_passing", + "risky": false + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Tests\\Unit\\ExampleTest::test_that_is_an_exception", + "lineage": [ + "Tests\\Unit\\ExampleTest", + "test_that_is_an_exception" + ], + "location": { + "file": "/var/www/html/tests/Unit/ExampleTest.php", + "line": 50 + }, + "attempt": { + "durationInNanoseconds": 861000, + "meta": { + "class": "Tests\\Unit\\ExampleTest", + "name": "test_that_is_an_exception", + "risky": false + }, + "status": { + "kind": "failed", + "message": "Exception: an exception was thrown", + "exception": "Exception", + "backtrace": [ + "/var/www/html/tests/Unit/ExampleTest.php:52" + ] + } + } + }, + { + "name": "Tests\\Unit\\ExampleTest::test_that_is_a_custom_exception", + "lineage": [ + "Tests\\Unit\\ExampleTest", + "test_that_is_a_custom_exception" + ], + "location": { + "file": "/var/www/html/tests/Unit/ExampleTest.php", + "line": 55 + }, + "attempt": { + "durationInNanoseconds": 118000, + "meta": { + "class": "Tests\\Unit\\ExampleTest", + "name": "test_that_is_a_custom_exception", + "risky": false + }, + "status": { + "kind": "failed", + "message": "Tests\\Unit\\CustomException: an exception was thrown", + "exception": "Tests\\Unit\\CustomException", + "backtrace": [ + "/var/www/html/tests/Unit/ExampleTest.php:57" + ] + } + } + }, + { + "name": "Tests\\Unit\\Nested\\ExampleTest::test_that_true_is_true", + "lineage": [ + "Tests\\Unit\\Nested\\ExampleTest", + "test_that_true_is_true" + ], + "location": { + "file": "/var/www/html/tests/Unit/Nested/ExampleTest.php", + "line": 14 + }, + "attempt": { + "durationInNanoseconds": 69000, + "meta": { + "class": "Tests\\Unit\\Nested\\ExampleTest", + "name": "test_that_true_is_true", + "risky": false + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "Tests\\Unit\\Nested\\ExampleTest::test_that_true_is_false", + "lineage": [ + "Tests\\Unit\\Nested\\ExampleTest", + "test_that_true_is_false" + ], + "location": { + "file": "/var/www/html/tests/Unit/Nested/ExampleTest.php", + "line": 19 + }, + "attempt": { + "durationInNanoseconds": 310000, + "meta": { + "class": "Tests\\Unit\\Nested\\ExampleTest", + "name": "test_that_true_is_false", + "risky": false + }, + "status": { + "kind": "failed", + "message": "Failed asserting that true is false.", + "exception": "PHPUnit\\Framework\\ExpectationFailedException", + "backtrace": [ + "/var/www/html/tests/Unit/Nested/ExampleTest.php:21" + ] + } + } + }, + { + "name": "Tests\\Unit\\Nested\\ExampleTest::test_that_is_slow", + "lineage": [ + "Tests\\Unit\\Nested\\ExampleTest", + "test_that_is_slow" + ], + "location": { + "file": "/var/www/html/tests/Unit/Nested/ExampleTest.php", + "line": 24 + }, + "attempt": { + "durationInNanoseconds": 1000279000, + "meta": { + "class": "Tests\\Unit\\Nested\\ExampleTest", + "name": "test_that_is_slow", + "risky": false + }, + "status": { + "kind": "failed", + "message": "Failed asserting that true is false.", + "exception": "PHPUnit\\Framework\\ExpectationFailedException", + "backtrace": [ + "/var/www/html/tests/Unit/Nested/ExampleTest.php:27" + ] + } + } + }, + { + "name": "Tests\\Unit\\Nested\\ExampleTest::test_with_no_assertion", + "lineage": [ + "Tests\\Unit\\Nested\\ExampleTest", + "test_with_no_assertion" + ], + "location": { + "file": "/var/www/html/tests/Unit/Nested/ExampleTest.php", + "line": 30 + }, + "attempt": { + "durationInNanoseconds": 68000, + "meta": { + "class": "Tests\\Unit\\Nested\\ExampleTest", + "name": "test_with_no_assertion", + "risky": true + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "Tests\\Unit\\Nested\\ExampleTest::test_with_printing", + "lineage": [ + "Tests\\Unit\\Nested\\ExampleTest", + "test_with_printing" + ], + "location": { + "file": "/var/www/html/tests/Unit/Nested/ExampleTest.php", + "line": 34 + }, + "attempt": { + "durationInNanoseconds": 3980000, + "meta": { + "class": "Tests\\Unit\\Nested\\ExampleTest", + "name": "test_with_printing", + "risky": false + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response", + "lineage": [ + "Tests\\Feature\\ExampleTest", + "test_the_application_returns_a_successful_response" + ], + "location": { + "file": "/var/www/html/tests/Feature/ExampleTest.php", + "line": 15 + }, + "attempt": { + "durationInNanoseconds": 421043000, + "meta": { + "class": "Tests\\Feature\\ExampleTest", + "name": "test_the_application_returns_a_successful_response", + "risky": false + }, + "status": { + "kind": "successful" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/PythonPytestParser Parse parses the sample file b/internal/captain/parsing/.snapshots/PythonPytestParser Parse parses the sample file new file mode 100644 index 0000000..f3ee007 --- /dev/null +++ b/internal/captain/parsing/.snapshots/PythonPytestParser Parse parses the sample file @@ -0,0 +1,539 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Python", + "kind": "pytest" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 23, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 8, + "pended": 0, + "quarantined": 0, + "skipped": 7, + "successful": 8, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "id": "nested/test_nested.py::TestSomeClass::test_some_method", + "name": "TestSomeClass::test_some_method", + "lineage": [ + "TestSomeClass", + "test_some_method" + ], + "location": { + "file": "nested/test_nested.py", + "line": 5 + }, + "attempt": { + "durationInNanoseconds": 249500, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "failed", + "message": "assert True == False", + "backtrace": [ + "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py:7" + ] + } + } + }, + { + "id": "nested/test_nested.py::TestSomeClassFailingSetup::test_some_method", + "name": "TestSomeClassFailingSetup::test_some_method", + "lineage": [ + "TestSomeClassFailingSetup", + "test_some_method" + ], + "location": { + "file": "nested/test_nested.py", + "line": 12 + }, + "attempt": { + "durationInNanoseconds": 338209, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "failed", + "message": "assert True == False", + "backtrace": [ + "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py:11" + ] + } + } + }, + { + "id": "nested/test_nested.py::TestSomeClassFailingSetup::test_some_skipped_method", + "name": "TestSomeClassFailingSetup::test_some_skipped_method", + "lineage": [ + "TestSomeClassFailingSetup", + "test_some_skipped_method" + ], + "location": { + "file": "nested/test_nested.py", + "line": 15 + }, + "attempt": { + "durationInNanoseconds": 80291, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "skipped", + "message": "to test skipping a test" + } + } + }, + { + "id": "nested/test_nested.py::TestSomeClassFailingTeardown::test_some_method", + "name": "TestSomeClassFailingTeardown::test_some_method", + "lineage": [ + "TestSomeClassFailingTeardown", + "test_some_method" + ], + "location": { + "file": "nested/test_nested.py", + "line": 23 + }, + "attempt": { + "durationInNanoseconds": 501500, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "failed", + "message": "assert True == False", + "backtrace": [ + "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py:22" + ] + } + } + }, + { + "id": "nested/test_nested.py::TestSomeClassFailingTeardown::test_some_skipped_method", + "name": "TestSomeClassFailingTeardown::test_some_skipped_method", + "lineage": [ + "TestSomeClassFailingTeardown", + "test_some_skipped_method" + ], + "location": { + "file": "nested/test_nested.py", + "line": 26 + }, + "attempt": { + "durationInNanoseconds": 116291, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "skipped", + "message": "to test skipping a test" + } + } + }, + { + "id": "nested/test_nested.py::test_exception", + "name": "test_exception", + "lineage": [ + "test_exception" + ], + "location": { + "file": "nested/test_nested.py", + "line": 43 + }, + "attempt": { + "durationInNanoseconds": 263167, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "failed", + "message": "Exception: Uh oh", + "backtrace": [ + "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py:45" + ] + } + } + }, + { + "id": "nested/test_nested.py::test_expected_fail_failing", + "name": "test_expected_fail_failing", + "lineage": [ + "test_expected_fail_failing" + ], + "location": { + "file": "nested/test_nested.py", + "line": 65 + }, + "attempt": { + "durationInNanoseconds": 236542, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "skipped", + "message": "Expected failure was skipped" + } + } + }, + { + "id": "nested/test_nested.py::test_expected_fail_passing", + "name": "test_expected_fail_passing", + "lineage": [ + "test_expected_fail_passing" + ], + "location": { + "file": "nested/test_nested.py", + "line": 69 + }, + "attempt": { + "durationInNanoseconds": 223416, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "nested/test_nested.py::test_nested_failing", + "name": "test_nested_failing", + "lineage": [ + "test_nested_failing" + ], + "location": { + "file": "nested/test_nested.py", + "line": 33 + }, + "attempt": { + "durationInNanoseconds": 234375, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "failed", + "message": "assert True == False", + "backtrace": [ + "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py:35" + ] + } + } + }, + { + "id": "nested/test_nested.py::test_nested_passing", + "name": "test_nested_passing", + "lineage": [ + "test_nested_passing" + ], + "location": { + "file": "nested/test_nested.py", + "line": 30 + }, + "attempt": { + "durationInNanoseconds": 308042, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "nested/test_nested.py::test_one", + "name": "test_one", + "lineage": [ + "test_one" + ], + "location": { + "file": "nested/test_nested.py", + "line": 36 + }, + "attempt": { + "durationInNanoseconds": 310792, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "nested/test_nested.py::test_parameterized[2+4-6]", + "name": "test_parameterized[2+4-6]", + "lineage": [ + "test_parameterized[2+4-6]" + ], + "location": { + "file": "nested/test_nested.py", + "line": 58 + }, + "attempt": { + "durationInNanoseconds": 392124, + "meta": { + "user_properties": { + "arbitrary": "value", + "expected": 6, + "test_input": "2+4" + } + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "nested/test_nested.py::test_parameterized[3+5-8]", + "name": "test_parameterized[3+5-8]", + "lineage": [ + "test_parameterized[3+5-8]" + ], + "location": { + "file": "nested/test_nested.py", + "line": 58 + }, + "attempt": { + "durationInNanoseconds": 586999, + "meta": { + "user_properties": { + "arbitrary": "value", + "expected": 8, + "test_input": "3+5" + } + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "nested/test_nested.py::test_parameterized[6*9-42]", + "name": "test_parameterized[6*9-42]", + "lineage": [ + "test_parameterized[6*9-42]" + ], + "location": { + "file": "nested/test_nested.py", + "line": 58 + }, + "attempt": { + "durationInNanoseconds": 529750, + "meta": { + "user_properties": { + "arbitrary": "value", + "expected": 42, + "test_input": "6*9" + } + }, + "status": { + "kind": "failed", + "message": "AssertionError: assert 54 == 42\n + where 54 = eval('6*9')", + "backtrace": [ + "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py:64" + ] + } + } + }, + { + "id": "nested/test_nested.py::test_skipped", + "name": "test_skipped", + "lineage": [ + "test_skipped" + ], + "location": { + "file": "nested/test_nested.py", + "line": 46 + }, + "attempt": { + "durationInNanoseconds": 100708, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "skipped", + "message": "to test skipping a test" + } + } + }, + { + "id": "nested/test_nested.py::test_skipped_conditionally", + "name": "test_skipped_conditionally", + "lineage": [ + "test_skipped_conditionally" + ], + "location": { + "file": "nested/test_nested.py", + "line": 54 + }, + "attempt": { + "durationInNanoseconds": 92250, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "skipped", + "message": "does not run on mac" + } + } + }, + { + "id": "nested/test_nested.py::test_skipped_no_reason", + "name": "test_skipped_no_reason", + "lineage": [ + "test_skipped_no_reason" + ], + "location": { + "file": "nested/test_nested.py", + "line": 50 + }, + "attempt": { + "durationInNanoseconds": 159666, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "skipped", + "message": "unconditional skip" + } + } + }, + { + "id": "nested/test_nested.py::test_slow", + "name": "test_slow", + "lineage": [ + "test_slow" + ], + "location": { + "file": "nested/test_nested.py", + "line": 39 + }, + "attempt": { + "durationInNanoseconds": 1505849000, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "nested/test_nested.py::test_strict_expected_fail_failing", + "name": "test_strict_expected_fail_failing", + "lineage": [ + "test_strict_expected_fail_failing" + ], + "location": { + "file": "nested/test_nested.py", + "line": 73 + }, + "attempt": { + "durationInNanoseconds": 207042, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "skipped", + "message": "Expected failure was skipped" + } + } + }, + { + "id": "nested/test_nested.py::test_strict_expected_fail_passing", + "name": "test_strict_expected_fail_passing", + "lineage": [ + "test_strict_expected_fail_passing" + ], + "location": { + "file": "nested/test_nested.py", + "line": 77 + }, + "attempt": { + "durationInNanoseconds": 230043, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "failed", + "message": "Unexpectedly passed" + } + } + }, + { + "id": "test_top_level.py::test_one", + "name": "test_one", + "lineage": [ + "test_one" + ], + "location": { + "file": "test_top_level.py", + "line": 6 + }, + "attempt": { + "durationInNanoseconds": 314167, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "test_top_level.py::test_top_level_failing", + "name": "test_top_level_failing", + "lineage": [ + "test_top_level_failing" + ], + "location": { + "file": "test_top_level.py", + "line": 3 + }, + "attempt": { + "durationInNanoseconds": 251666, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "failed", + "message": "assert True == False", + "backtrace": [ + "/Users/kylekthompson/src/captain-examples/pytest/test_top_level.py:5" + ] + } + } + }, + { + "id": "test_top_level.py::test_top_level_passing", + "name": "test_top_level_passing", + "lineage": [ + "test_top_level_passing" + ], + "location": { + "file": "test_top_level.py", + "line": 0 + }, + "attempt": { + "durationInNanoseconds": 332000, + "meta": { + "user_properties": {} + }, + "status": { + "kind": "successful" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/PythonUnitTestParser Parse handles files with multiple test suites b/internal/captain/parsing/.snapshots/PythonUnitTestParser Parse handles files with multiple test suites new file mode 100644 index 0000000..2a967a9 --- /dev/null +++ b/internal/captain/parsing/.snapshots/PythonUnitTestParser Parse handles files with multiple test suites @@ -0,0 +1,64 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Python", + "kind": "unittest" + }, + "summary": { + "status": { + "kind": "successful" + }, + "tests": 2, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 0, + "pended": 0, + "quarantined": 0, + "skipped": 0, + "successful": 2, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "test.TSF.test_choice", + "lineage": [ + "test.TSF", + "test_choice" + ], + "location": { + "file": "", + "line": 0 + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "successful" + }, + "startedAt": "0001-01-01T00:00:00Z", + "finishedAt": "0001-01-01T00:00:00Z" + } + }, + { + "name": "test.TS2F.test_choice", + "lineage": [ + "test.TS2F", + "test_choice" + ], + "location": { + "file": "", + "line": 0 + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "successful" + }, + "startedAt": "0001-01-01T00:00:00Z", + "finishedAt": "0001-01-01T00:00:00Z" + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/PythonUnitTestParser Parse parses the sample file b/internal/captain/parsing/.snapshots/PythonUnitTestParser Parse parses the sample file new file mode 100644 index 0000000..b74cd81 --- /dev/null +++ b/internal/captain/parsing/.snapshots/PythonUnitTestParser Parse parses the sample file @@ -0,0 +1,131 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Python", + "kind": "unittest" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 5, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 1, + "pended": 0, + "quarantined": 0, + "skipped": 1, + "successful": 3, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "test.TestSequenceFunctions.test_choice", + "lineage": [ + "test.TestSequenceFunctions", + "test_choice" + ], + "location": { + "file": "test.py", + "line": 23 + }, + "attempt": { + "durationInNanoseconds": 2406000000, + "status": { + "kind": "successful" + }, + "startedAt": "2023-02-22T16:52:15Z", + "finishedAt": "2023-02-22T16:52:17.406Z" + } + }, + { + "name": "test.TestSequenceFunctions.test_sample", + "lineage": [ + "test.TestSequenceFunctions", + "test_sample" + ], + "location": { + "file": "test.py", + "line": 32 + }, + "attempt": { + "durationInNanoseconds": 2402000000, + "status": { + "kind": "successful" + }, + "startedAt": "2023-02-22T16:52:20Z", + "finishedAt": "2023-02-22T16:52:22.402Z" + } + }, + { + "name": "test.TestSequenceFunctions.test_shuffle", + "lineage": [ + "test.TestSequenceFunctions", + "test_shuffle" + ], + "location": { + "file": "test.py", + "line": 14 + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "successful" + }, + "startedAt": "2023-02-22T16:52:20Z", + "finishedAt": "2023-02-22T16:52:20Z" + } + }, + { + "name": "test.TestSequenceFunctions.test_failure", + "lineage": [ + "test.TestSequenceFunctions", + "test_failure" + ], + "location": { + "file": "test.py", + "line": 28 + }, + "attempt": { + "durationInNanoseconds": 2405000000, + "status": { + "kind": "failed", + "message": "False is not true", + "exception": "AssertionError", + "backtrace": [ + "Traceback (most recent call last):", + "File \"/Users/tommygraves/code/unittest/test.py\", line 30, in test_failure", + "self.assertTrue(False)", + "AssertionError: False is not true", + "" + ] + }, + "startedAt": "2023-02-22T16:52:18Z", + "finishedAt": "2023-02-22T16:52:20.405Z" + } + }, + { + "name": "test.TestSequenceFunctions.test_skipped", + "lineage": [ + "test.TestSequenceFunctions", + "test_skipped" + ], + "location": { + "file": "test.py", + "line": 10 + }, + "attempt": { + "durationInNanoseconds": 0, + "status": { + "kind": "skipped", + "message": "demonstrating skipping" + }, + "startedAt": "2023-02-22T16:52:20Z", + "finishedAt": "2023-02-22T16:52:20Z" + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/RWXParser Parse parses the sample file b/internal/captain/parsing/.snapshots/RWXParser Parse parses the sample file new file mode 100644 index 0000000..e91625b --- /dev/null +++ b/internal/captain/parsing/.snapshots/RWXParser Parse parses the sample file @@ -0,0 +1,111 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "RSpec" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 3, + "flaky": 0, + "otherErrors": 3, + "retries": 1, + "canceled": 0, + "failed": 1, + "pended": 0, + "quarantined": 1, + "skipped": 0, + "successful": 1, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "id": "./spec/some/path/foo_spec.rb:12", + "name": "Sky::Moon when it's dark out is bright", + "lineage": [ + "Sky::Moon", + "when it's dark out", + "is bright" + ], + "location": { + "file": "./spec/some/path/foo_spec.rb", + "line": 12 + }, + "attempt": { + "durationInNanoseconds": 1100300300, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/some/path/foo_spec.rb:20", + "name": "Sky::Moon when it's dark out is not bright", + "lineage": [ + "Sky::Moon", + "when it's dark out", + "is not bright" + ], + "location": { + "file": "./spec/some/path/foo_spec.rb", + "line": 20 + }, + "attempt": { + "durationInNanoseconds": 1200000000, + "status": { + "kind": "quarantined", + "originalStatus": { + "kind": "failed", + "message": "The moon is bright" + } + } + } + }, + { + "name": "Sky::Moon does not exist", + "attempt": { + "durationInNanoseconds": 1200000000, + "meta": { + "env": "foo" + }, + "status": { + "kind": "successful" + }, + "finishedAt": "2022-11-15T15:01:49Z" + }, + "pastAttempts": [ + { + "durationInNanoseconds": 1500000000, + "status": { + "kind": "failed", + "message": "The moon is in the sky" + }, + "finishedAt": "2022-11-15T07:01:34Z" + } + ] + } + ], + "otherErrors": [ + { + "message": "something broke" + }, + { + "exception": "FooError", + "location": { + "file": "./some/path/to/file.rb", + "line": 10 + }, + "message": "An error occurred" + } + ], + "derivedFrom": [ + { + "originalFilePath": "./some/path/to/file.json", + "contents": "base64encodedoriginalfile", + "groupNumber": 1 + } + ] +} diff --git a/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a failing element b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a failing element new file mode 100644 index 0000000..04a5366 --- /dev/null +++ b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a failing element @@ -0,0 +1,60 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "Cucumber" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 1, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 1, + "pended": 0, + "quarantined": 0, + "skipped": 0, + "successful": 0, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "Rule Sample \u003e A failing example", + "lineage": [ + "Rule Sample", + "A failing example" + ], + "location": { + "file": "features/rule.feature", + "line": 13 + }, + "attempt": { + "durationInNanoseconds": 26528000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing", + "line": 12 + } + ] + }, + "status": { + "kind": "failed", + "message": "\nexpected true\n got false\n (RSpec::Expectations::ExpectationNotMetError)\n./features/step_definitions/steps.rb:13:in `\"some results should be there\"'\nfeatures/rule.feature:16:in `some results should be there'" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a passing element b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a passing element new file mode 100644 index 0000000..a73ad51 --- /dev/null +++ b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a passing element @@ -0,0 +1,55 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "Cucumber" + }, + "summary": { + "status": { + "kind": "successful" + }, + "tests": 1, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 0, + "pended": 0, + "quarantined": 0, + "skipped": 0, + "successful": 1, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "Rule Sample \u003e A passing example", + "lineage": [ + "Rule Sample", + "A passing example" + ], + "location": { + "file": "features/rule.feature", + "line": 7 + }, + "attempt": { + "durationInNanoseconds": 1189000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + "status": { + "kind": "successful" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a pending element b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a pending element new file mode 100644 index 0000000..e07c8a7 --- /dev/null +++ b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a pending element @@ -0,0 +1,55 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "Cucumber" + }, + "summary": { + "status": { + "kind": "successful" + }, + "tests": 1, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 0, + "pended": 0, + "quarantined": 0, + "skipped": 1, + "successful": 0, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "Rule Sample \u003e A pending example", + "lineage": [ + "Rule Sample", + "A pending example" + ], + "location": { + "file": "features/rule.feature", + "line": 18 + }, + "attempt": { + "durationInNanoseconds": 158000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + "status": { + "kind": "skipped" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a skipped element b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a skipped element new file mode 100644 index 0000000..3b140d4 --- /dev/null +++ b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a skipped element @@ -0,0 +1,244 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "Cucumber" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 7, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 3, + "pended": 0, + "quarantined": 0, + "skipped": 3, + "successful": 1, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "Rule Sample \u003e A passing example", + "lineage": [ + "Rule Sample", + "A passing example" + ], + "location": { + "file": "features/rule.feature", + "line": 7 + }, + "attempt": { + "durationInNanoseconds": 1189000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "Rule Sample \u003e A failing example", + "lineage": [ + "Rule Sample", + "A failing example" + ], + "location": { + "file": "features/rule.feature", + "line": 13 + }, + "attempt": { + "durationInNanoseconds": 26528000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing", + "line": 12 + } + ] + }, + "status": { + "kind": "failed", + "message": "\nexpected true\n got false\n (RSpec::Expectations::ExpectationNotMetError)\n./features/step_definitions/steps.rb:13:in `\"some results should be there\"'\nfeatures/rule.feature:16:in `some results should be there'" + } + } + }, + { + "name": "Rule Sample \u003e A pending example", + "lineage": [ + "Rule Sample", + "A pending example" + ], + "location": { + "file": "features/rule.feature", + "line": 18 + }, + "attempt": { + "durationInNanoseconds": 158000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Rule Sample \u003e A skipped example", + "lineage": [ + "Rule Sample", + "A skipped example" + ], + "location": { + "file": "features/rule.feature", + "line": 23 + }, + "attempt": { + "durationInNanoseconds": 139000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Rule Sample \u003e An undefined example", + "lineage": [ + "Rule Sample", + "An undefined example" + ], + "location": { + "file": "features/rule.feature", + "line": 28 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Rule Sample \u003e With a failing before hook", + "lineage": [ + "Rule Sample", + "With a failing before hook" + ], + "location": { + "file": "features/rule.feature", + "line": 34 + }, + "attempt": { + "durationInNanoseconds": 141000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing_before_hook", + "line": 33 + } + ] + }, + "status": { + "kind": "failed", + "message": "failed in before hook (RuntimeError)\n./features/step_definitions/steps.rb:25:in `Before'" + } + } + }, + { + "name": "Rule Sample \u003e With a failing after hook", + "lineage": [ + "Rule Sample", + "With a failing after hook" + ], + "location": { + "file": "features/rule.feature", + "line": 41 + }, + "attempt": { + "durationInNanoseconds": 178000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing_after_hook", + "line": 40 + } + ] + }, + "status": { + "kind": "failed", + "message": "failed in after hook (RuntimeError)\n./features/step_definitions/steps.rb:29:in `After'" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a undefined element b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a undefined element new file mode 100644 index 0000000..7400688 --- /dev/null +++ b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses a undefined element @@ -0,0 +1,55 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "Cucumber" + }, + "summary": { + "status": { + "kind": "successful" + }, + "tests": 1, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 0, + "pended": 0, + "quarantined": 0, + "skipped": 1, + "successful": 0, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "Rule Sample \u003e An undefined example", + "lineage": [ + "Rule Sample", + "An undefined example" + ], + "location": { + "file": "features/rule.feature", + "line": 28 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + "status": { + "kind": "skipped" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses an element with a failing after hook b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses an element with a failing after hook new file mode 100644 index 0000000..36f2fe6 --- /dev/null +++ b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses an element with a failing after hook @@ -0,0 +1,60 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "Cucumber" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 1, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 1, + "pended": 0, + "quarantined": 0, + "skipped": 0, + "successful": 0, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "Rule Sample \u003e With a failing after hook", + "lineage": [ + "Rule Sample", + "With a failing after hook" + ], + "location": { + "file": "features/rule.feature", + "line": 41 + }, + "attempt": { + "durationInNanoseconds": 178000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing_after_hook", + "line": 40 + } + ] + }, + "status": { + "kind": "failed", + "message": "failed in after hook (RuntimeError)\n./features/step_definitions/steps.rb:29:in `After'" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses an element with a failing before hook b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses an element with a failing before hook new file mode 100644 index 0000000..660c5c5 --- /dev/null +++ b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses an element with a failing before hook @@ -0,0 +1,60 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "Cucumber" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 1, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 1, + "pended": 0, + "quarantined": 0, + "skipped": 0, + "successful": 0, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "Rule Sample \u003e With a failing before hook", + "lineage": [ + "Rule Sample", + "With a failing before hook" + ], + "location": { + "file": "features/rule.feature", + "line": 34 + }, + "attempt": { + "durationInNanoseconds": 141000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing_before_hook", + "line": 33 + } + ] + }, + "status": { + "kind": "failed", + "message": "failed in before hook (RuntimeError)\n./features/step_definitions/steps.rb:25:in `Before'" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses the sample file b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses the sample file new file mode 100644 index 0000000..3b140d4 --- /dev/null +++ b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses the sample file @@ -0,0 +1,244 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "Cucumber" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 7, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 3, + "pended": 0, + "quarantined": 0, + "skipped": 3, + "successful": 1, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "Rule Sample \u003e A passing example", + "lineage": [ + "Rule Sample", + "A passing example" + ], + "location": { + "file": "features/rule.feature", + "line": 7 + }, + "attempt": { + "durationInNanoseconds": 1189000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "Rule Sample \u003e A failing example", + "lineage": [ + "Rule Sample", + "A failing example" + ], + "location": { + "file": "features/rule.feature", + "line": 13 + }, + "attempt": { + "durationInNanoseconds": 26528000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing", + "line": 12 + } + ] + }, + "status": { + "kind": "failed", + "message": "\nexpected true\n got false\n (RSpec::Expectations::ExpectationNotMetError)\n./features/step_definitions/steps.rb:13:in `\"some results should be there\"'\nfeatures/rule.feature:16:in `some results should be there'" + } + } + }, + { + "name": "Rule Sample \u003e A pending example", + "lineage": [ + "Rule Sample", + "A pending example" + ], + "location": { + "file": "features/rule.feature", + "line": 18 + }, + "attempt": { + "durationInNanoseconds": 158000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Rule Sample \u003e A skipped example", + "lineage": [ + "Rule Sample", + "A skipped example" + ], + "location": { + "file": "features/rule.feature", + "line": 23 + }, + "attempt": { + "durationInNanoseconds": 139000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Rule Sample \u003e An undefined example", + "lineage": [ + "Rule Sample", + "An undefined example" + ], + "location": { + "file": "features/rule.feature", + "line": 28 + }, + "attempt": { + "durationInNanoseconds": 0, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Rule Sample \u003e With a failing before hook", + "lineage": [ + "Rule Sample", + "With a failing before hook" + ], + "location": { + "file": "features/rule.feature", + "line": 34 + }, + "attempt": { + "durationInNanoseconds": 141000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing_before_hook", + "line": 33 + } + ] + }, + "status": { + "kind": "failed", + "message": "failed in before hook (RuntimeError)\n./features/step_definitions/steps.rb:25:in `Before'" + } + } + }, + { + "name": "Rule Sample \u003e With a failing after hook", + "lineage": [ + "Rule Sample", + "With a failing after hook" + ], + "location": { + "file": "features/rule.feature", + "line": 41 + }, + "attempt": { + "durationInNanoseconds": 178000, + "meta": { + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing_after_hook", + "line": 40 + } + ] + }, + "status": { + "kind": "failed", + "message": "failed in after hook (RuntimeError)\n./features/step_definitions/steps.rb:29:in `After'" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses the sample file that uses background b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses the sample file that uses background new file mode 100644 index 0000000..b9c0619 --- /dev/null +++ b/internal/captain/parsing/.snapshots/RubyCucumberParser Parse parses the sample file that uses background @@ -0,0 +1,184 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "Cucumber" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 7, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 3, + "pended": 0, + "quarantined": 0, + "skipped": 3, + "successful": 1, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "Background Sample \u003e A passing example", + "lineage": [ + "Background Sample", + "A passing example" + ], + "location": { + "file": "features/background.feature", + "line": 6 + }, + "attempt": { + "durationInNanoseconds": 584000, + "meta": { + "tags": null + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "Background Sample \u003e A failing example", + "lineage": [ + "Background Sample", + "A failing example" + ], + "location": { + "file": "features/background.feature", + "line": 12 + }, + "attempt": { + "durationInNanoseconds": 19087000, + "meta": { + "tags": [ + { + "name": "@failing", + "line": 11 + } + ] + }, + "status": { + "kind": "failed", + "message": "\nexpected true\n got false\n (RSpec::Expectations::ExpectationNotMetError)\n./features/step_definitions/steps.rb:17:in `\"some results should be there\"'\nfeatures/background.feature:15:in `some results should be there'" + } + } + }, + { + "name": "Background Sample \u003e A pending example", + "lineage": [ + "Background Sample", + "A pending example" + ], + "location": { + "file": "features/background.feature", + "line": 17 + }, + "attempt": { + "durationInNanoseconds": 60000, + "meta": { + "tags": null + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Background Sample \u003e A skipped example", + "lineage": [ + "Background Sample", + "A skipped example" + ], + "location": { + "file": "features/background.feature", + "line": 22 + }, + "attempt": { + "durationInNanoseconds": 55000, + "meta": { + "tags": null + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Background Sample \u003e An undefined example", + "lineage": [ + "Background Sample", + "An undefined example" + ], + "location": { + "file": "features/background.feature", + "line": 27 + }, + "attempt": { + "durationInNanoseconds": 4000, + "meta": { + "tags": null + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "Background Sample \u003e With a failing before hook", + "lineage": [ + "Background Sample", + "With a failing before hook" + ], + "location": { + "file": "features/background.feature", + "line": 33 + }, + "attempt": { + "durationInNanoseconds": 64000, + "meta": { + "tags": [ + { + "name": "@failing_before_hook", + "line": 32 + } + ] + }, + "status": { + "kind": "failed", + "message": "failed in before hook (RuntimeError)\n./features/step_definitions/steps.rb:29:in `Before'" + } + } + }, + { + "name": "Background Sample \u003e With a failing after hook", + "lineage": [ + "Background Sample", + "With a failing after hook" + ], + "location": { + "file": "features/background.feature", + "line": 39 + }, + "attempt": { + "durationInNanoseconds": 65000, + "meta": { + "tags": [ + { + "name": "@failing_after_hook", + "line": 38 + } + ] + }, + "status": { + "kind": "failed", + "message": "failed in after hook (RuntimeError)\n./features/step_definitions/steps.rb:33:in `After'" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/RubyMinitestParser Parse parses the sample file b/internal/captain/parsing/.snapshots/RubyMinitestParser Parse parses the sample file new file mode 100644 index 0000000..a1103d3 --- /dev/null +++ b/internal/captain/parsing/.snapshots/RubyMinitestParser Parse parses the sample file @@ -0,0 +1,266 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "minitest" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 11, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 4, + "pended": 0, + "quarantined": 0, + "skipped": 2, + "successful": 5, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "name": "TestFailing#test_fails", + "lineage": [ + "TestFailing", + "test_fails" + ], + "location": { + "file": "test/failing_test.rb", + "line": 4 + }, + "attempt": { + "durationInNanoseconds": 23000, + "meta": { + "assertions": 1 + }, + "status": { + "kind": "failed", + "message": "Expected false to be truthy.", + "exception": "Minitest::Assertion", + "backtrace": [ + "/Users/kylekthompson/src/captain-examples/minitest/test/failing_test.rb:5" + ] + } + } + }, + { + "name": "TestFailing#test_raises", + "lineage": [ + "TestFailing", + "test_raises" + ], + "location": { + "file": "test/failing_test.rb", + "line": 8 + }, + "attempt": { + "durationInNanoseconds": 43000, + "meta": { + "assertions": 0 + }, + "status": { + "kind": "failed", + "message": "RuntimeError: uh oh", + "exception": "RuntimeError", + "backtrace": [ + "/Users/kylekthompson/src/captain-examples/minitest/test/failing_test.rb:9:in `test_raises'" + ] + } + } + }, + { + "name": "TestFailing#test_raises_custom", + "lineage": [ + "TestFailing", + "test_raises_custom" + ], + "location": { + "file": "test/failing_test.rb", + "line": 12 + }, + "attempt": { + "durationInNanoseconds": 26000, + "meta": { + "assertions": 0 + }, + "status": { + "kind": "failed", + "message": "RWX::Example::MyError: uh oh", + "exception": "RWX::Example::MyError", + "backtrace": [ + "/Users/kylekthompson/src/captain-examples/minitest/test/failing_test.rb:13:in `test_raises_custom'" + ] + } + } + }, + { + "name": "TestFailing#test_assert_equal_fails", + "lineage": [ + "TestFailing", + "test_assert_equal_fails" + ], + "location": { + "file": "test/failing_test.rb", + "line": 25 + }, + "attempt": { + "durationInNanoseconds": 35000, + "meta": { + "assertions": 1 + }, + "status": { + "kind": "failed", + "message": "Expected: 2\n Actual: 1", + "exception": "Minitest::Assertion", + "backtrace": [ + "/Users/kylekthompson/src/captain-examples/minitest/test/rwx/example_test.rb:26" + ] + } + } + }, + { + "name": "RWX::TestExample#test_is_slow", + "lineage": [ + "RWX::TestExample", + "test_is_slow" + ], + "location": { + "file": "test/rwx/example_test.rb", + "line": 25 + }, + "attempt": { + "durationInNanoseconds": 1503885000, + "meta": { + "assertions": 1 + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "RWX::TestExample#test_returns_one", + "lineage": [ + "RWX::TestExample", + "test_returns_one" + ], + "location": { + "file": "test/rwx/example_test.rb", + "line": 21 + }, + "attempt": { + "durationInNanoseconds": 37000, + "meta": { + "assertions": 1 + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "RWX::TestExample#test_raises_custom_error", + "lineage": [ + "RWX::TestExample", + "test_raises_custom_error" + ], + "location": { + "file": "test/rwx/example_test.rb", + "line": 15 + }, + "attempt": { + "durationInNanoseconds": 55000, + "meta": { + "assertions": 1 + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "RWX::TestExample#test_raises_runtime_error", + "lineage": [ + "RWX::TestExample", + "test_raises_runtime_error" + ], + "location": { + "file": "test/rwx/example_test.rb", + "line": 9 + }, + "attempt": { + "durationInNanoseconds": 19000, + "meta": { + "assertions": 1 + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "TestSkipped#test_not_skipped", + "lineage": [ + "TestSkipped", + "test_not_skipped" + ], + "location": { + "file": "test/skipped_test.rb", + "line": 4 + }, + "attempt": { + "durationInNanoseconds": 14000, + "meta": { + "assertions": 1 + }, + "status": { + "kind": "successful" + } + } + }, + { + "name": "TestSkipped#test_skipped_failing", + "lineage": [ + "TestSkipped", + "test_skipped_failing" + ], + "location": { + "file": "test/skipped_test.rb", + "line": 8 + }, + "attempt": { + "durationInNanoseconds": 68000, + "meta": { + "assertions": 0 + }, + "status": { + "kind": "skipped" + } + } + }, + { + "name": "TestSkipped#test_skipped_passing", + "lineage": [ + "TestSkipped", + "test_skipped_passing" + ], + "location": { + "file": "test/skipped_test.rb", + "line": 13 + }, + "attempt": { + "durationInNanoseconds": 37000, + "meta": { + "assertions": 0 + }, + "status": { + "kind": "skipped" + } + } + } + ] +} diff --git a/internal/captain/parsing/.snapshots/RubyRSpecParser Parse parses the sample file b/internal/captain/parsing/.snapshots/RubyRSpecParser Parse parses the sample file new file mode 100644 index 0000000..1815a7c --- /dev/null +++ b/internal/captain/parsing/.snapshots/RubyRSpecParser Parse parses the sample file @@ -0,0 +1,3121 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "RSpec" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 72, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 36, + "pended": 24, + "quarantined": 0, + "skipped": 0, + "successful": 12, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "id": "./spec/examples/class_spec.rb[1:1]", + "name": "Tests::Case has top-level passing tests", + "lineage": [ + "Tests::Case", + "has top-level passing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 30795000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 5 + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:2]", + "name": "Tests::Case has top-level failing tests", + "lineage": [ + "Tests::Case", + "has top-level failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 6114000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 9 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/class_spec.rb:10:in `block (2 levels) in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:3]", + "name": "Tests::Case has top-level aggregated failing tests", + "lineage": [ + "Tests::Case", + "has top-level aggregated failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3131000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 13 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:14:in `block (2 levels) in \u003ctop (required)\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:15:in `block (2 levels) in \u003ctop (required)\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:4]", + "name": "Tests::Case has top-level skipped tests", + "lineage": [ + "Tests::Case", + "has top-level skipped tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 6000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 18 + }, + "status": { + "kind": "pended", + "message": "Temporarily skipped with xit" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:5]", + "name": "Tests::Case has top-level passing pended tests", + "lineage": [ + "Tests::Case", + "has top-level passing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3167000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 23 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/class_spec.rb:23" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:6]", + "name": "Tests::Case has top-level failing pended tests", + "lineage": [ + "Tests::Case", + "has top-level failing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2972000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 28 + }, + "status": { + "kind": "pended", + "message": "for a reason" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:1]", + "name": "Tests::Case behaves like shared examples has top-level passing tests", + "lineage": [ + "Tests::Case behaves like shared examples", + "has top-level passing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2982000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 2 + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:2]", + "name": "Tests::Case behaves like shared examples has top-level failing tests", + "lineage": [ + "Tests::Case behaves like shared examples", + "has top-level failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2971000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 6 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:3]", + "name": "Tests::Case behaves like shared examples has top-level aggregated failing tests", + "lineage": [ + "Tests::Case behaves like shared examples", + "has top-level aggregated failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2540000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 10 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:4]", + "name": "Tests::Case behaves like shared examples has top-level skipped tests", + "lineage": [ + "Tests::Case behaves like shared examples", + "has top-level skipped tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 4000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 15 + }, + "status": { + "kind": "pended", + "message": "Temporarily skipped with xit" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:5]", + "name": "Tests::Case behaves like shared examples has top-level passing pended tests", + "lineage": [ + "Tests::Case behaves like shared examples", + "has top-level passing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3049000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 20 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:20" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:6]", + "name": "Tests::Case behaves like shared examples has top-level failing pended tests", + "lineage": [ + "Tests::Case behaves like shared examples", + "has top-level failing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3055000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 25 + }, + "status": { + "kind": "pended", + "message": "for a reason" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:1]", + "name": "Tests::Case behaves like shared examples within a context has passing tests", + "lineage": [ + "Tests::Case behaves like shared examples within a context", + "has passing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2859000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 31 + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:2]", + "name": "Tests::Case behaves like shared examples within a context has failing tests", + "lineage": [ + "Tests::Case behaves like shared examples within a context", + "has failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2979000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 35 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:3]", + "name": "Tests::Case behaves like shared examples within a context has aggregated failing tests", + "lineage": [ + "Tests::Case behaves like shared examples within a context", + "has aggregated failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2438000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 39 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:4]", + "name": "Tests::Case behaves like shared examples within a context has skipped tests", + "lineage": [ + "Tests::Case behaves like shared examples within a context", + "has skipped tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 4000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 44 + }, + "status": { + "kind": "pended", + "message": "Temporarily skipped with xit" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:5]", + "name": "Tests::Case behaves like shared examples within a context has passing pended tests", + "lineage": [ + "Tests::Case behaves like shared examples within a context", + "has passing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2583000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 49 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:49" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:6]", + "name": "Tests::Case behaves like shared examples within a context has failing pended tests", + "lineage": [ + "Tests::Case behaves like shared examples within a context", + "has failing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3151000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 54 + }, + "status": { + "kind": "pended", + "message": "for a reason" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:1]", + "name": "Tests::Case within a context has passing tests", + "lineage": [ + "Tests::Case within a context", + "has passing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2502000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 36 + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:2]", + "name": "Tests::Case within a context has failing tests", + "lineage": [ + "Tests::Case within a context", + "has failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3060000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 40 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/class_spec.rb:41:in `block (3 levels) in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:3]", + "name": "Tests::Case within a context has aggregated failing tests", + "lineage": [ + "Tests::Case within a context", + "has aggregated failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3055000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 44 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:45:in `block (3 levels) in \u003ctop (required)\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:46:in `block (3 levels) in \u003ctop (required)\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:4]", + "name": "Tests::Case within a context has skipped tests", + "lineage": [ + "Tests::Case within a context", + "has skipped tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 4000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 49 + }, + "status": { + "kind": "pended", + "message": "Temporarily skipped with xit" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:5]", + "name": "Tests::Case within a context has passing pended tests", + "lineage": [ + "Tests::Case within a context", + "has passing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2014000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 54 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/class_spec.rb:54" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:6]", + "name": "Tests::Case within a context has failing pended tests", + "lineage": [ + "Tests::Case within a context", + "has failing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2398000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 59 + }, + "status": { + "kind": "pended", + "message": "for a reason" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:1]", + "name": "Tests::Case within a context behaves like shared examples has top-level passing tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples", + "has top-level passing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2994000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 2 + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:2]", + "name": "Tests::Case within a context behaves like shared examples has top-level failing tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples", + "has top-level failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2811000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 6 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:3]", + "name": "Tests::Case within a context behaves like shared examples has top-level aggregated failing tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples", + "has top-level aggregated failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2654000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 10 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:4]", + "name": "Tests::Case within a context behaves like shared examples has top-level skipped tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples", + "has top-level skipped tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 15 + }, + "status": { + "kind": "pended", + "message": "Temporarily skipped with xit" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:5]", + "name": "Tests::Case within a context behaves like shared examples has top-level passing pended tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples", + "has top-level passing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3018000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 20 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:20" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:6]", + "name": "Tests::Case within a context behaves like shared examples has top-level failing pended tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples", + "has top-level failing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2221000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 25 + }, + "status": { + "kind": "pended", + "message": "for a reason" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:1]", + "name": "Tests::Case within a context behaves like shared examples within a context has passing tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples within a context", + "has passing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2212000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 31 + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:2]", + "name": "Tests::Case within a context behaves like shared examples within a context has failing tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples within a context", + "has failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2861000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 35 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:3]", + "name": "Tests::Case within a context behaves like shared examples within a context has aggregated failing tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples within a context", + "has aggregated failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3040000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 39 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:4]", + "name": "Tests::Case within a context behaves like shared examples within a context has skipped tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples within a context", + "has skipped tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 4000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 44 + }, + "status": { + "kind": "pended", + "message": "Temporarily skipped with xit" + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:5]", + "name": "Tests::Case within a context behaves like shared examples within a context has passing pended tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples within a context", + "has passing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2672000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 49 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:49" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:6]", + "name": "Tests::Case within a context behaves like shared examples within a context has failing pended tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples within a context", + "has failing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2959000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 54 + }, + "status": { + "kind": "pended", + "message": "for a reason" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:1]", + "name": "some string has top-level passing tests", + "lineage": [ + "some string", + "has top-level passing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2110000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 4 + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:2]", + "name": "some string has top-level failing tests", + "lineage": [ + "some string", + "has top-level failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2240000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 8 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/string_spec.rb:9:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:3]", + "name": "some string has top-level aggregated failing tests", + "lineage": [ + "some string", + "has top-level aggregated failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2886000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 12 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:13:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:14:in `block (2 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:4]", + "name": "some string has top-level skipped tests", + "lineage": [ + "some string", + "has top-level skipped tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 4000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 17 + }, + "status": { + "kind": "pended", + "message": "Temporarily skipped with xit" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:5]", + "name": "some string has top-level passing pended tests", + "lineage": [ + "some string", + "has top-level passing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2922000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 22 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/string_spec.rb:22" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:6]", + "name": "some string has top-level failing pended tests", + "lineage": [ + "some string", + "has top-level failing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2704000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 27 + }, + "status": { + "kind": "pended", + "message": "for a reason" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:1]", + "name": "some string behaves like shared examples has top-level passing tests", + "lineage": [ + "some string behaves like shared examples", + "has top-level passing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2700000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 2 + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:2]", + "name": "some string behaves like shared examples has top-level failing tests", + "lineage": [ + "some string behaves like shared examples", + "has top-level failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2541000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 6 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:3]", + "name": "some string behaves like shared examples has top-level aggregated failing tests", + "lineage": [ + "some string behaves like shared examples", + "has top-level aggregated failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2879000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 10 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:4]", + "name": "some string behaves like shared examples has top-level skipped tests", + "lineage": [ + "some string behaves like shared examples", + "has top-level skipped tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 15 + }, + "status": { + "kind": "pended", + "message": "Temporarily skipped with xit" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:5]", + "name": "some string behaves like shared examples has top-level passing pended tests", + "lineage": [ + "some string behaves like shared examples", + "has top-level passing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2721000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 20 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:20" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:6]", + "name": "some string behaves like shared examples has top-level failing pended tests", + "lineage": [ + "some string behaves like shared examples", + "has top-level failing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3249000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 25 + }, + "status": { + "kind": "pended", + "message": "for a reason" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:1]", + "name": "some string behaves like shared examples within a context has passing tests", + "lineage": [ + "some string behaves like shared examples within a context", + "has passing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2793000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 31 + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:2]", + "name": "some string behaves like shared examples within a context has failing tests", + "lineage": [ + "some string behaves like shared examples within a context", + "has failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2262000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 35 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:3]", + "name": "some string behaves like shared examples within a context has aggregated failing tests", + "lineage": [ + "some string behaves like shared examples within a context", + "has aggregated failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2288000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 39 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:4]", + "name": "some string behaves like shared examples within a context has skipped tests", + "lineage": [ + "some string behaves like shared examples within a context", + "has skipped tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 44 + }, + "status": { + "kind": "pended", + "message": "Temporarily skipped with xit" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:5]", + "name": "some string behaves like shared examples within a context has passing pended tests", + "lineage": [ + "some string behaves like shared examples within a context", + "has passing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2546000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 49 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:49" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:6]", + "name": "some string behaves like shared examples within a context has failing pended tests", + "lineage": [ + "some string behaves like shared examples within a context", + "has failing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2156000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 54 + }, + "status": { + "kind": "pended", + "message": "for a reason" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:1]", + "name": "some string within a context has passing tests", + "lineage": [ + "some string within a context", + "has passing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2272000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 35 + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:2]", + "name": "some string within a context has failing tests", + "lineage": [ + "some string within a context", + "has failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2598000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 39 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/string_spec.rb:40:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:3]", + "name": "some string within a context has aggregated failing tests", + "lineage": [ + "some string within a context", + "has aggregated failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2331000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 43 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:44:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:45:in `block (3 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:4]", + "name": "some string within a context has skipped tests", + "lineage": [ + "some string within a context", + "has skipped tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 4000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 48 + }, + "status": { + "kind": "pended", + "message": "Temporarily skipped with xit" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:5]", + "name": "some string within a context has passing pended tests", + "lineage": [ + "some string within a context", + "has passing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2118000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 53 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/string_spec.rb:53" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:6]", + "name": "some string within a context has failing pended tests", + "lineage": [ + "some string within a context", + "has failing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2429000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 58 + }, + "status": { + "kind": "pended", + "message": "for a reason" + } + } + }, + { + "name": "some string within a context behaves like shared examples has top-level passing tests", + "lineage": [ + "some string within a context behaves like shared examples", + "has top-level passing tests" + ], + "location": { + "file": "./spec/examples/shared_examples.rb" + }, + "attempt": { + "durationInNanoseconds": 1960000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 2 + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:2]", + "name": "some string within a context behaves like shared examples has top-level failing tests", + "lineage": [ + "some string within a context behaves like shared examples", + "has top-level failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 1989000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 6 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:3]", + "name": "some string within a context behaves like shared examples has top-level aggregated failing tests", + "lineage": [ + "some string within a context behaves like shared examples", + "has top-level aggregated failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2512000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 10 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:4]", + "name": "some string within a context behaves like shared examples has top-level skipped tests", + "lineage": [ + "some string within a context behaves like shared examples", + "has top-level skipped tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 4000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 15 + }, + "status": { + "kind": "pended", + "message": "Temporarily skipped with xit" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:5]", + "name": "some string within a context behaves like shared examples has top-level passing pended tests", + "lineage": [ + "some string within a context behaves like shared examples", + "has top-level passing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2402000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 20 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:20" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:6]", + "name": "some string within a context behaves like shared examples has top-level failing pended tests", + "lineage": [ + "some string within a context behaves like shared examples", + "has top-level failing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2197000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 25 + }, + "status": { + "kind": "pended", + "message": "for a reason" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:1]", + "name": "some string within a context behaves like shared examples within a context has passing tests", + "lineage": [ + "some string within a context behaves like shared examples within a context", + "has passing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2170000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 31 + }, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:2]", + "name": "some string within a context behaves like shared examples within a context has failing tests", + "lineage": [ + "some string within a context behaves like shared examples within a context", + "has failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2212000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 35 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:3]", + "name": "some string within a context behaves like shared examples within a context has aggregated failing tests", + "lineage": [ + "some string within a context behaves like shared examples within a context", + "has aggregated failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3165000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 39 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:4]", + "name": "some string within a context behaves like shared examples within a context has skipped tests", + "lineage": [ + "some string within a context behaves like shared examples within a context", + "has skipped tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 44 + }, + "status": { + "kind": "pended", + "message": "Temporarily skipped with xit" + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:5]", + "name": "some string within a context behaves like shared examples within a context has passing pended tests", + "lineage": [ + "some string within a context behaves like shared examples within a context", + "has passing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2212000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 49 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:49" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:6]", + "name": "some string within a context behaves like shared examples within a context has failing pended tests", + "lineage": [ + "some string within a context behaves like shared examples within a context", + "has failing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2257000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 54 + }, + "status": { + "kind": "pended", + "message": "for a reason" + } + } + } + ] +} diff --git a/internal/captain/parsing/dot_net_xunit_parser.go b/internal/captain/parsing/dot_net_xunit_parser.go new file mode 100644 index 0000000..38d189a --- /dev/null +++ b/internal/captain/parsing/dot_net_xunit_parser.go @@ -0,0 +1,272 @@ +package parsing + +import ( + "encoding/xml" + "fmt" + "io" + "math" + "os" + "regexp" + "strconv" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type DotNetxUnitParser struct{} + +type DotNetxUnitCdataContent struct { + Contents string `xml:",cdata"` +} + +type DotNetxUnitTrait struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` + XMLName xml.Name `xml:"trait"` +} + +type DotNetxUnitFailure struct { + // children + Message *DotNetxUnitCdataContent `xml:"message"` + Stacktrace *DotNetxUnitCdataContent `xml:"stack-trace"` + + // attributes + ExceptionType *string `xml:"exception-type,attr"` + + XMLName xml.Name `xml:"failure"` +} + +type DotNetxUnitTest struct { + // children + Traits []DotNetxUnitTrait `xml:"traits>trait"` + Failure *DotNetxUnitFailure `xml:"failure"` + Output *DotNetxUnitCdataContent `xml:"output"` + SkippedReason *DotNetxUnitCdataContent `xml:"reason"` + SourceFile *string `xml:"source-file"` + SourceLine *string `xml:"source-line"` + + // attributes + ID *string `xml:"id,attr"` + Method *string `xml:"method,attr"` + Name string `xml:"name,attr"` + Result string `xml:"result,attr"` // Pass, Fail, Skip, NotRun + Time float64 `xml:"time,attr"` + TimeRtf *string `xml:"time-rtf,attr"` + Type *string `xml:"type,attr"` + + XMLName xml.Name `xml:"test"` +} + +type DotNetxUnitCollection struct { + // children + Tests []DotNetxUnitTest `xml:"test"` + + // attributes + ID *string `xml:"id,attr"` + Name string `xml:"name,attr"` + Failed int `xml:"failed,attr"` + NotRun *int `xml:"not-run,attr"` + Passed int `xml:"passed,attr"` + Skipped int `xml:"skipped,attr"` + Time float64 `xml:"time,attr"` + TimeRtf *string `xml:"time-rtf,attr"` + Total int `xml:"total,attr"` + + XMLName xml.Name `xml:"collection"` +} + +type DotNetxUnitError struct { + // children + Failure DotNetxUnitFailure `xml:"failure"` + + // attributes + Name *string `xml:"name,attr"` + Type string `xml:"type,attr"` + + XMLName xml.Name `xml:"error"` +} + +type DotNetxUnitAssembly struct { + // children + Collections []DotNetxUnitCollection `xml:"collection"` + Errors []DotNetxUnitError `xml:"errors>error"` + + // attributes + ConfigFile *string `xml:"config-file,attr"` + Environment string `xml:"environment,attr"` + ErrorsCount int `xml:"errors,attr"` + Failed int `xml:"failed,attr"` + FinishRtf *string `xml:"finish-rtf,attr"` + ID *string `xml:"id,attr"` + Name string `xml:"name,attr"` + NotRun *int `xml:"not-run,attr"` + Passed int `xml:"passed,attr"` + RunDate string `xml:"run-date,attr"` + RunTime string `xml:"run-time,attr"` + Skipped int `xml:"skipped,attr"` + StartRtf *string `xml:"start-rtf,attr"` + TargetFramework *string `xml:"target-framework,attr"` + TestFramework string `xml:"test-framework,attr"` + Time float64 `xml:"time,attr"` + TimeRtf *string `xml:"time-rtf,attr"` + Total int `xml:"total,attr"` + + XMLName xml.Name `xml:"assembly"` +} + +type DotNetxUnitTestResults struct { + // children + Assemblies []DotNetxUnitAssembly `xml:"assembly"` + + // attributes + Computer *string `xml:"computer,attr"` + FinishRtf *string `xml:"finish-rtf,attr"` + ID *string `xml:"id,attr"` + SchemaVersion *int `xml:"schema-version,attr"` + StartRtf *string `xml:"start-rtf,attr"` + Timestamp string `xml:"timestamp,attr"` + User *string `xml:"user,attr"` + + XMLName xml.Name `xml:"assemblies"` +} + +var dotNetxUnitAssemblyNameRegexp = regexp.MustCompile(fmt.Sprintf(`[^%s]+$`, string(os.PathSeparator))) + +var dotNetxUnitNewlineRegexp = regexp.MustCompile(`\r?\n`) + +func (p DotNetxUnitParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testResults DotNetxUnitTestResults + + if err := xml.NewDecoder(data).Decode(&testResults); err != nil { + return nil, errors.NewInputError("Unable to parse test results as XML: %s", err) + } + if len(testResults.Assemblies) > 0 && testResults.Assemblies[0].Collections == nil { + return nil, errors.NewInputError("The test suites in the XML do not appear to match xUnit.NET XML") + } + + tests := make([]v1.Test, 0) + otherErrors := make([]v1.OtherError, 0) + for _, assembly := range testResults.Assemblies { + assemblyName := dotNetxUnitAssemblyNameRegexp.FindString(assembly.Name) + + for _, collection := range assembly.Collections { + for _, testCase := range collection.Tests { + duration := time.Duration(math.Round(testCase.Time * float64(time.Second))) + + meta := map[string]any{ + "assembly": assemblyName, + "type": *testCase.Type, + "method": *testCase.Method, + } + for _, trait := range testCase.Traits { + meta[fmt.Sprintf("trait-%v", trait.Name)] = trait.Value + } + + var location *v1.Location + if testCase.SourceFile != nil { + var line *int + if testCase.SourceLine != nil { + if parsedLine, err := strconv.Atoi(*testCase.SourceLine); err == nil { + line = &parsedLine + } + } + location = &v1.Location{File: *testCase.SourceFile, Line: line} + } + + var stdout *string + if testCase.Output != nil { + testCase := testCase + stdout = &testCase.Output.Contents + } + + var status v1.TestStatus + switch testCase.Result { + case "Pass": + status = v1.NewSuccessfulTestStatus() + case "Fail": + var message *string + var exception *string + var backtrace []string + if testCase.Failure != nil { + exception = testCase.Failure.ExceptionType + message, backtrace = p.FailureDetails(*testCase.Failure) + } + + status = v1.NewFailedTestStatus(message, exception, backtrace) + case "Skip": + var message *string + if testCase.SkippedReason != nil { + testCase := testCase + message = &testCase.SkippedReason.Contents + } + + status = v1.NewSkippedTestStatus(message) + case "NotRun": + status = v1.NewSkippedTestStatus(nil) + default: + return nil, errors.NewInputError("Unexpected result %q for test %v", testCase.Result, testCase) + } + + tests = append( + tests, + v1.Test{ + Scope: &assemblyName, + ID: testCase.ID, + Name: testCase.Name, + Location: location, + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: meta, + Status: status, + Stdout: stdout, + }, + }, + ) + } + } + + for _, assemblyError := range assembly.Errors { + message, backtrace := p.FailureDetails(assemblyError.Failure) + + if message == nil { + defaultMessage := fmt.Sprintf("An error occurred during %v", assemblyError.Type) + message = &defaultMessage + } + + otherErrors = append(otherErrors, v1.OtherError{ + Backtrace: backtrace, + Exception: assemblyError.Failure.ExceptionType, + Message: *message, + Meta: map[string]any{ + "assembly": assemblyName, + "type": assemblyError.Type, + }, + }) + } + } + + return v1.NewTestResults( + v1.DotNetxUnitFramework, + tests, + otherErrors, + ), nil +} + +func (p DotNetxUnitParser) FailureDetails(failure DotNetxUnitFailure) (*string, []string) { + var message *string + if failure.Message != nil { + message = &failure.Message.Contents + } + + var backtrace []string + if failure.Stacktrace != nil { + backtrace = dotNetxUnitNewlineRegexp.Split(failure.Stacktrace.Contents, -1) + for i, line := range backtrace { + backtrace[i] = strings.TrimSpace(line) + } + } + + return message, backtrace +} diff --git a/internal/captain/parsing/dot_net_xunit_parser_test.go b/internal/captain/parsing/dot_net_xunit_parser_test.go new file mode 100644 index 0000000..c21b180 --- /dev/null +++ b/internal/captain/parsing/dot_net_xunit_parser_test.go @@ -0,0 +1,376 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + "time" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("DotNetxUnitParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/xunit_dot_net.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.DotNetxUnitParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("errors on malformed XML", func() { + testResults, err := parsing.DotNetxUnitParser{}.Parse(strings.NewReader(``)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as XML")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.DotNetxUnitParser{}.Parse(strings.NewReader(``)) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + testResults, err = parsing.DotNetxUnitParser{}.Parse( + strings.NewReader(``), + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring("The test suites in the XML do not appear to match xUnit.NET XML"), + ) + Expect(testResults).To(BeNil()) + }) + + It("can extract a detailed successful test", func() { + testResults, err := parsing.DotNetxUnitParser{}.Parse(strings.NewReader( + ` + + + + + some/path/to/source.cs + 12 + + + + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + line := 12 + id := "some-id" + duration := time.Duration(6370900) + testType := "NullAssertsTests+Null" + testMethod := "Success" + stdout := "line 1\nline 2\nline 3" + assembly := "AssemblyName.dll" + Expect(testResults.Tests[0]).To(Equal( + v1.Test{ + Scope: &assembly, + ID: &id, + Name: "NullAssertsTests+Null.Success", + Location: &v1.Location{File: "some/path/to/source.cs", Line: &line}, + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: map[string]any{ + "assembly": assembly, + "type": testType, + "method": testMethod, + "trait-some-trait": "some-value", + "trait-other-trait": "other-value", + }, + Status: v1.NewSuccessfulTestStatus(), + Stdout: &stdout, + }, + }, + )) + }) + + It("can extract a failed test", func() { + testResults, err := parsing.DotNetxUnitParser{}.Parse(strings.NewReader( + ` + + + + + + + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + duration := time.Duration(6370900) + message := "Some message here" + exception := "AssertionException" + assembly := "AssemblyName.dll" + testType := "NullAssertsTests+Null" + testMethod := "Success" + Expect(testResults.Tests[0]).To(Equal( + v1.Test{ + Scope: &assembly, + Name: "NullAssertsTests+Null.Success", + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: map[string]any{ + "assembly": assembly, + "type": testType, + "method": testMethod, + }, + Status: v1.NewFailedTestStatus(&message, &exception, []string{"Some trace", "other line"}), + }, + }, + )) + }) + + It("can extract a failed test without details", func() { + testResults, err := parsing.DotNetxUnitParser{}.Parse(strings.NewReader( + ` + + + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + duration := time.Duration(6370900) + assembly := "AssemblyName.dll" + testType := "NullAssertsTests+Null" + testMethod := "Success" + Expect(testResults.Tests[0]).To(Equal( + v1.Test{ + Scope: &assembly, + Name: "NullAssertsTests+Null.Success", + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: map[string]any{ + "assembly": assembly, + "type": testType, + "method": testMethod, + }, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + )) + }) + + It("can extract a skipped test", func() { + testResults, err := parsing.DotNetxUnitParser{}.Parse(strings.NewReader( + ` + + + + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + duration := time.Duration(6370900) + message := "Some reason here" + assembly := "AssemblyName.dll" + testType := "NullAssertsTests+Null" + testMethod := "Success" + Expect(testResults.Tests[0]).To(Equal( + v1.Test{ + Scope: &assembly, + Name: "NullAssertsTests+Null.Success", + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: map[string]any{ + "assembly": assembly, + "type": testType, + "method": testMethod, + }, + Status: v1.NewSkippedTestStatus(&message), + }, + }, + )) + }) + + It("can extract a not-run test", func() { + testResults, err := parsing.DotNetxUnitParser{}.Parse(strings.NewReader( + ` + + + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + duration := time.Duration(6370900) + assembly := "AssemblyName.dll" + testType := "NullAssertsTests+Null" + testMethod := "Success" + Expect(testResults.Tests[0]).To(Equal( + v1.Test{ + Scope: &assembly, + Name: "NullAssertsTests+Null.Success", + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: map[string]any{ + "assembly": assembly, + "type": testType, + "method": testMethod, + }, + Status: v1.NewSkippedTestStatus(nil), + }, + }, + )) + }) + + It("errors on other results", func() { + testResults, err := parsing.DotNetxUnitParser{}.Parse(strings.NewReader( + ` + + + + + + + + + `, + )) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(`Unexpected result "wat"`)) + Expect(testResults).To(BeNil()) + }) + + It("can extract other errors", func() { + testResults, err := parsing.DotNetxUnitParser{}.Parse(strings.NewReader( + ` + + + + + + + + + + + + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + exception := "SomeException" + Expect(testResults.OtherErrors).To(Equal( + []v1.OtherError{ + { + Backtrace: []string{"Some trace", "other line"}, + Exception: &exception, + Message: "Some message here", + Meta: map[string]any{ + "assembly": "AssemblyName.dll", + "type": "error-type-one", + }, + }, + { + Message: "An error occurred during error-type-two", + Meta: map[string]any{ + "assembly": "AssemblyName.dll", + "type": "error-type-two", + }, + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/parsing/elixir_exunit_parser.go b/internal/captain/parsing/elixir_exunit_parser.go new file mode 100644 index 0000000..3df04bc --- /dev/null +++ b/internal/captain/parsing/elixir_exunit_parser.go @@ -0,0 +1,151 @@ +package parsing + +import ( + "encoding/xml" + "fmt" + "io" + "math" + "regexp" + "strconv" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type ElixirExUnitParser struct{} + +type ElixirExUnitFailure struct { + Message *string `xml:"message,attr"` + Contents *string `xml:",chardata"` +} + +type ElixirExUnitSkipped struct { + Message *string `xml:"message,attr"` +} + +type ElixirExUnitTestCase struct { + ClassName string `xml:"classname,attr,omitempty"` + Error *ElixirExUnitFailure `xml:"error"` + Failure *ElixirExUnitFailure `xml:"failure"` + Name string `xml:"name,attr"` + Skipped *ElixirExUnitSkipped `xml:"skipped"` + Time float64 `xml:"time,attr"` + File *string `xml:"file,attr"` + + XMLName xml.Name `xml:"testcase"` +} + +type ElixirExUnitProperty struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} + +type ElixirExUnitTestSuite struct { + Errors int `xml:"errors,attr"` + Failures int `xml:"failures,attr"` + Name string `xml:"name,attr"` + Skipped int `xml:"skipped,attr"` + TestCases []ElixirExUnitTestCase `xml:"testcase"` + Properties []ElixirExUnitProperty `xml:"properties>property"` + Tests *int `xml:"tests,attr"` + Time float64 `xml:"time,attr,omitempty"` + + XMLName xml.Name `xml:"testsuite"` +} + +type ElixirExUnitTestResults struct { + TestSuites []ElixirExUnitTestSuite `xml:"testsuite"` + XMLName xml.Name `xml:"testsuites"` +} + +var elixirExUnitNewlineRegexp = regexp.MustCompile(`\r?\n`) + +func (p ElixirExUnitParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testResults ElixirExUnitTestResults + + if err := xml.NewDecoder(data).Decode(&testResults); err != nil { + return nil, errors.NewInputError("Unable to parse test results as XML: %s", err) + } + + tests := make([]v1.Test, 0) + for _, testSuite := range testResults.TestSuites { + for _, testCase := range testSuite.TestCases { + name := fmt.Sprintf("%v %v", testCase.ClassName, testCase.Name) + duration := time.Duration(math.Round(testCase.Time * float64(time.Second))) + + var status v1.TestStatus + switch { + case testCase.Failure != nil: + status = p.NewFailedTestStatus(*testCase.Failure) + case testCase.Error != nil: + status = p.NewFailedTestStatus(*testCase.Error) + case testCase.Skipped != nil: + status = v1.NewSkippedTestStatus(testCase.Skipped.Message) + default: + status = v1.NewSuccessfulTestStatus() + } + + fileAndLine := strings.Split(*testCase.File, ":") + line, err := strconv.Atoi(fileAndLine[1]) + if err != nil { + return nil, errors.NewInputError("Elixir ExUnit file attribute is improperly formatted; expected file:line") + } + location := v1.Location{File: fileAndLine[0], Line: &line} + + tests = append( + tests, + v1.Test{ + Name: name, + Location: &location, + Attempt: v1.TestAttempt{ + Duration: &duration, + Status: status, + }, + }, + ) + } + } + + return v1.NewTestResults( + v1.ElixirExUnitFramework, + tests, + nil, + ), nil +} + +func (p ElixirExUnitParser) NewFailedTestStatus(failure ElixirExUnitFailure) v1.TestStatus { + failureMessage := failure.Message + + // The lines in here look something like: + // + // 1) test throws an exception (ExunitexampleWeb.ExceptionTest) + // test/exunitexample_web/views/exception_test.exs:7 + // ** (throw) "some exception" + // code: throw "some exception" + // stacktrace: + // test/exunitexample_web/views/exception_test.exs:8: (test) + // + // So, we'll search for the 'stacktrace:' and take the next line as the backtrace + var lines []string + if failure.Contents != nil { + lines = elixirExUnitNewlineRegexp.Split(*failure.Contents, -1) + } + if len(lines) == 0 { + return v1.NewFailedTestStatus(failureMessage, nil, nil) + } + + var backtrace *string + for i, line := range lines { + if strings.Contains(line, "stacktrace:") && i+1 < len(lines) { + trimmedBacktrace := strings.TrimSpace(lines[i+1]) + backtrace = &trimmedBacktrace + } + } + if backtrace == nil { + return v1.NewFailedTestStatus(failureMessage, nil, nil) + } + + return v1.NewFailedTestStatus(failureMessage, nil, []string{*backtrace}) +} diff --git a/internal/captain/parsing/elixir_exunit_parser_test.go b/internal/captain/parsing/elixir_exunit_parser_test.go new file mode 100644 index 0000000..a21d403 --- /dev/null +++ b/internal/captain/parsing/elixir_exunit_parser_test.go @@ -0,0 +1,79 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("ElixirExUnitParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/exunit.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.ElixirExUnitParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses empty files", func() { + testResults, err := parsing.ElixirExUnitParser{}.Parse( + strings.NewReader(``), + ) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults.Tests).To(HaveLen(0)) + + testResults, err = parsing.ElixirExUnitParser{}.Parse( + strings.NewReader(``), + ) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults.Tests).To(HaveLen(0)) + }) + + It("errors on malformed XML", func() { + testResults, err := parsing.ElixirExUnitParser{}.Parse(strings.NewReader(``)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as XML")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.ElixirExUnitParser{}.Parse( + strings.NewReader(``), + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring("Unable to parse test results as XML"), + ) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.ElixirExUnitParser{}.Parse( + strings.NewReader(``), + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring("Unable to parse test results as XML"), + ) + Expect(testResults).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/go_ginkgo_parser.go b/internal/captain/parsing/go_ginkgo_parser.go new file mode 100644 index 0000000..5715427 --- /dev/null +++ b/internal/captain/parsing/go_ginkgo_parser.go @@ -0,0 +1,116 @@ +package parsing + +import ( + "encoding/json" + "io" + "regexp" + + ginkgo "github.com/onsi/ginkgo/v2/types" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type GoGinkgoParser struct{} + +var goGinkgoNewlineRegexp = regexp.MustCompile(`\r?\n`) + +func (p GoGinkgoParser) Parse(data io.Reader) (*v1.TestResults, error) { + var ginkgoReport []ginkgo.Report + + if err := json.NewDecoder(data).Decode(&ginkgoReport); err != nil { + return nil, errors.NewInputError("Unable to parse test results as JSON: %s", err) + } + if len(ginkgoReport) == 0 { + return nil, errors.NewInputError("Report is empty; unable to guarantee the results are Ginkgo") + } + + tests := make([]v1.Test, 0) + otherErrors := make([]v1.OtherError, 0) + for _, suite := range ginkgoReport { + if suite.SuitePath == "" { + return nil, errors.NewInputError("Report does not look like a Ginkgo report") + } + + for _, reason := range suite.SpecialSuiteFailureReasons { + location := v1.Location{File: suite.SuitePath} + otherErrors = append(otherErrors, v1.OtherError{Message: reason, Location: &location}) + } + + for _, specReport := range suite.SpecReports { + // https://github.com/onsi/ginkgo/blob/60240d1b6479bb8bba394760ec0034b98d5c3f7b/types/types.go#L279-L283 + lineage := make([]string, 0) + lineage = append(lineage, specReport.ContainerHierarchyTexts...) + if specReport.LeafNodeText != "" { + lineage = append(lineage, specReport.LeafNodeText) + } + + line := specReport.LineNumber() + location := v1.Location{File: specReport.FileName(), Line: &line} + + var status v1.TestStatus + switch specReport.State { + case ginkgo.SpecStateInvalid: + return nil, errors.NewInputError("Invalid spec state: %v", specReport.State) + case ginkgo.SpecStatePending: + status = v1.NewPendedTestStatus(nil) + case ginkgo.SpecStateSkipped: + message := specReport.Failure.Message + status = v1.NewSkippedTestStatus(&message) + case ginkgo.SpecStatePassed: + status = v1.NewSuccessfulTestStatus() + case ginkgo.SpecStateFailed: + message := specReport.Failure.Message + backtrace := goGinkgoNewlineRegexp.Split(specReport.Failure.Location.FullStackTrace, -1) + status = v1.NewFailedTestStatus(&message, nil, backtrace) + case ginkgo.SpecStateAborted: + status = v1.NewCanceledTestStatus() + case ginkgo.SpecStatePanicked: + message := "The test panicked" + status = v1.NewFailedTestStatus(&message, nil, nil) + case ginkgo.SpecStateInterrupted: + status = v1.NewCanceledTestStatus() + case ginkgo.SpecStateTimedout: + status = v1.NewTimedOutTestStatus(nil, nil, nil) + default: + return nil, errors.NewInputError("Unknown spec state: %v", specReport.State) + } + + duration := specReport.RunTime + stderr := specReport.CapturedStdOutErr + stdout := specReport.CapturedGinkgoWriterOutput + startedAt := specReport.StartTime + finishedAt := specReport.EndTime + pastAttempts := make([]v1.TestAttempt, len(specReport.AdditionalFailures)) + for i, failure := range specReport.AdditionalFailures { + message := failure.Failure.Message + backtrace := goGinkgoNewlineRegexp.Split(failure.Failure.Location.FullStackTrace, -1) + pastAttempts[i] = v1.TestAttempt{ + Status: v1.NewFailedTestStatus(&message, nil, backtrace), + } + } + + tests = append(tests, v1.Test{ + Name: specReport.FullText(), + Lineage: lineage, + Location: &location, + Attempt: v1.TestAttempt{ + Duration: &duration, + Status: status, + Meta: map[string]any{"labels": specReport.Labels()}, + Stderr: &stderr, + Stdout: &stdout, + StartedAt: &startedAt, + FinishedAt: &finishedAt, + }, + PastAttempts: pastAttempts, + }) + } + } + + return v1.NewTestResults( + v1.GoGinkgoFramework, + tests, + otherErrors, + ), nil +} diff --git a/internal/captain/parsing/go_ginkgo_parser_test.go b/internal/captain/parsing/go_ginkgo_parser_test.go new file mode 100644 index 0000000..9f19aa7 --- /dev/null +++ b/internal/captain/parsing/go_ginkgo_parser_test.go @@ -0,0 +1,84 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GoGinkgoParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/ginkgo.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.GoGinkgoParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses other errors", func() { + fixture, err := os.Open("../../test/fixtures/ginkgo_with_other_errors.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.GoGinkgoParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("errors on malformed JSON", func() { + testResults, err := parsing.GoGinkgoParser{}.Parse(strings.NewReader(`[{]`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as JSON")) + Expect(testResults).To(BeNil()) + }) + + It("errors on JSON that doesn't look like Ginkgo", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.GoGinkgoParser{}.Parse(strings.NewReader(`[]`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Report is empty; unable to guarantee the results are Ginkgo")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.GoGinkgoParser{}.Parse(strings.NewReader( + ` + [ + {}, + {"SuitePath": "foo"} + ] + `, + )) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Report does not look like a Ginkgo report")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.GoGinkgoParser{}.Parse(strings.NewReader( + ` + [ + {"SuitePath": "foo"}, + {} + ] + `, + )) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Report does not look like a Ginkgo report")) + Expect(testResults).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/go_test_parser.go b/internal/captain/parsing/go_test_parser.go new file mode 100644 index 0000000..310d38f --- /dev/null +++ b/internal/captain/parsing/go_test_parser.go @@ -0,0 +1,137 @@ +package parsing + +import ( + "bufio" + "encoding/json" + "io" + "math" + "sort" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type GoTestParser struct{} + +// https://pkg.go.dev/cmd/test2json +type GoTestTestOutput struct { + Time time.Time `json:"Time"` + Action *string `json:"Action"` // bench, cont, fail, output, pass, pause, run, skip + Package *string `json:"Package"` + Test *string `json:"Test"` // optional, depends if the output is about a specific test or package + Output *string `json:"Output"` // set for output events + Elapsed *float64 `json:"Elapsed"` // set for pass and fail events +} + +func (p GoTestParser) Parse(data io.Reader) (*v1.TestResults, error) { + testsByPackage := map[string]map[string]v1.Test{} + scanner := bufio.NewScanner(data) + for scanner.Scan() { + text := strings.TrimSpace(scanner.Text()) + if !strings.HasPrefix(text, "{") { + continue + } + + var testOutput GoTestTestOutput + if err := json.NewDecoder(strings.NewReader(text)).Decode(&testOutput); err != nil { + continue + } + + if testOutput.Action == nil || testOutput.Package == nil { + return nil, errors.NewInputError("Test results do not look like go test") + } + + // We don't care about package-level stats/info + if testOutput.Test == nil { + continue + } + + if _, ok := testsByPackage[*testOutput.Package]; !ok { + testsByPackage[*testOutput.Package] = map[string]v1.Test{} + } + + testPackage := *testOutput.Package + existingTest, ok := testsByPackage[testPackage][*testOutput.Test] + if !ok { + // Default to timed out. Tests that complete normally will have their status + // overridden by a "pass", "fail", or "skip" action. Tests that are killed by + // `go test -timeout` never receive a terminal action and will remain timed out. + timedOutMessage := "Captain inferred that this test timed out because " + + "go test never emitted a pass, fail, or skip event for it." + existingTest = v1.Test{ + Scope: &testPackage, + Name: *testOutput.Test, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": testPackage}, + Status: v1.NewTimedOutTestStatus(&timedOutMessage, nil, nil), + }, + } + } + + switch *testOutput.Action { + case "bench": + // docs indicate this exists, but I can't seem to reproduce + case "cont": + // no-op + case "fail": + duration := time.Duration(math.Round(*testOutput.Elapsed * float64(time.Second))) + existingTest.Attempt.Duration = &duration + existingTest.Attempt.Status = v1.NewFailedTestStatus(nil, nil, nil) + case "output": + if testOutput.Output == nil { + return nil, errors.NewInputError("JSON with action of output is missing output: %v", testOutput) + } + + if existingTest.Attempt.Stdout == nil { + existingTest.Attempt.Stdout = testOutput.Output + } else { + newStdout := strings.Join([]string{*existingTest.Attempt.Stdout, *testOutput.Output}, "") + existingTest.Attempt.Stdout = &newStdout + } + case "pass": + duration := time.Duration(math.Round(*testOutput.Elapsed * float64(time.Second))) + existingTest.Attempt.Duration = &duration + existingTest.Attempt.Status = v1.NewSuccessfulTestStatus() + case "pause": + // no-op + case "run": + // no-op + case "skip": + duration := time.Duration(math.Round(*testOutput.Elapsed * float64(time.Second))) + existingTest.Attempt.Duration = &duration + existingTest.Attempt.Status = v1.NewSkippedTestStatus(nil) + default: + return nil, errors.NewInputError("Unexpected test action: %v", *testOutput.Action) + } + + testsByPackage[*testOutput.Package][*testOutput.Test] = existingTest + } + + tests := make([]v1.Test, 0) + for _, testsByName := range testsByPackage { + for _, test := range testsByName { + tests = append(tests, test) + } + } + + if len(tests) == 0 { + return nil, errors.NewInputError("Did not see any tests, so we cannot be sure it is go test output") + } + + // For determinism + sort.Slice(tests, func(i, j int) bool { + if tests[i].Name == tests[j].Name { + return tests[i].Attempt.Meta["package"].(string) < tests[j].Attempt.Meta["package"].(string) + } + + return tests[i].Name < tests[j].Name + }) + + return v1.NewTestResults( + v1.GoTestFramework, + tests, + nil, + ), nil +} diff --git a/internal/captain/parsing/go_test_parser_test.go b/internal/captain/parsing/go_test_parser_test.go new file mode 100644 index 0000000..a1a3d22 --- /dev/null +++ b/internal/captain/parsing/go_test_parser_test.go @@ -0,0 +1,107 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GoTestParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/go_test.jsonl") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.GoTestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("sets the package as the scope", func() { + fixture, err := os.Open("../../test/fixtures/go_test.jsonl") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.GoTestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + test := testResults.Tests[0] + Expect(*test.Scope).To(SatisfyAny( + Equal("github.com/captain-examples/go-testing/internal/pkg1"), + Equal("github.com/captain-examples/go-testing/internal/pkg2"), + )) + }) + + It("marks timed out tests that have no pass/fail action", func() { + fixture, err := os.Open("../../test/fixtures/go_test_timeout.jsonl") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.GoTestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + Expect(testResults.Tests).To(HaveLen(2)) + + var passedTest, timedOutTest v1.Test + for _, t := range testResults.Tests { + switch t.Name { + case "TestLoadConfig_Success": + passedTest = t + case "TestThreeSeconds": + timedOutTest = t + } + } + + Expect(passedTest.Attempt.Status.Kind).To(Equal(v1.TestStatusSuccessful)) + Expect(timedOutTest.Attempt.Status.Kind).To(Equal(v1.TestStatusTimedOut)) + Expect(timedOutTest.Attempt.Status.Message).ToNot(BeNil()) + Expect(*timedOutTest.Attempt.Status.Message).To(ContainSubstring("Captain inferred that this test timed out")) + }) + + It("errors on malformed JSON with no remnants of Go Test JSON", func() { + testResults, err := parsing.GoTestParser{}.Parse(strings.NewReader(`asdfasdfsdf`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring( + "Did not see any tests, so we cannot be sure it is go test output", + )) + Expect(testResults).To(BeNil()) + }) + + It("errors on JSON that doesn't look like go test", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.GoTestParser{}.Parse(strings.NewReader(`{}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Test results do not look like go test")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.GoTestParser{}.Parse(strings.NewReader( + ` + {"Action":"run"} + `, + )) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Test results do not look like go test")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.GoTestParser{}.Parse(strings.NewReader( + ` + {"Package":"one"} + `, + )) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Test results do not look like go test")) + Expect(testResults).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/javascript_cucumber_json_parser.go b/internal/captain/parsing/javascript_cucumber_json_parser.go new file mode 100644 index 0000000..caf9b5a --- /dev/null +++ b/internal/captain/parsing/javascript_cucumber_json_parser.go @@ -0,0 +1,170 @@ +package parsing + +import ( + "encoding/json" + "io" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JavaScriptCucumberJSONParser struct{} + +type JavaScriptCucumberJSONFeature struct { + URI string `json:"uri"` + ID string `json:"id"` + Keyword string `json:"keyword"` + Name string `json:"name"` + Description string `json:"description"` + Line int `json:"line"` + Tags []struct { + Name string `json:"name"` + Line *int `json:"line"` + } `json:"tags"` + Elements []struct { + ID string `json:"id"` + Keyword string `json:"keyword"` + Name string `json:"name"` + Description string `json:"description"` + Line int `json:"line"` + Steps []struct { + Arguments []struct{} `json:"arguments"` + Keyword string `json:"keyword"` + Line int `json:"line"` + Name string `json:"name"` + Match struct { + Location string `json:"location"` + } `json:"match"` + Result *struct { + Status string `json:"status"` + Duration *int `json:"duration"` + ErrorMessage *string `json:"error_message"` + } `json:"result"` + } `json:"steps"` + Tags []struct { + Name string `json:"name"` + Line *int `json:"line"` + } `json:"tags"` + Type string `json:"type"` + } `json:"elements"` +} + +func (p JavaScriptCucumberJSONParser) Parse(data io.Reader) (*v1.TestResults, error) { + var cucumberFeatures []JavaScriptCucumberJSONFeature + + if err := json.NewDecoder(data).Decode(&cucumberFeatures); err != nil { + return nil, errors.NewInputError("Unable to parse test results as JSON: %s", err) + } + if len(cucumberFeatures) == 0 { + return nil, errors.NewInputError("No test results were found in the JSON") + } + + foundOneResult := false +outer: + for _, feature := range cucumberFeatures { + for _, element := range feature.Elements { + for _, step := range element.Steps { + if step.Result != nil { + foundOneResult = true + break outer + } + } + } + } + + if !foundOneResult { + return nil, errors.NewInputError("Found features, but no results in the JSON") + } + + tests := make([]v1.Test, 0) + otherErrors := make([]v1.OtherError, 0) + + // https://github.com/cucumber/messages/blob/3a3c605d0ef7df53012d78ddc0796dbae3df17a1/javascript/src/getWorstTestStepResult.ts#L18-L26 + // failed, then ambiguous, then undefined, then pending, then skipped, then passed + priority := map[string]int{ + "failed": 6, + "ambiguous": 5, + "undefined": 4, + "pending": 3, + "skipped": 2, + "passed": 1, + } + + for _, feature := range cucumberFeatures { + for _, element := range feature.Elements { + var stepStatus string + var status v1.TestStatus + duration := time.Duration(0) + + for _, step := range element.Steps { + if step.Result.Duration != nil { + duration += time.Duration(*step.Result.Duration * int(time.Nanosecond)) + } + + if step.Result == nil { + break + } + + if _, ok := priority[step.Result.Status]; !ok { + return nil, errors.NewInputError( + "Unexpected status %v for cucumber step %v", + step.Result.Status, + step, + ) + } + + if stepStatus == "" || priority[stepStatus] < priority[step.Result.Status] { + stepStatus = step.Result.Status + switch step.Result.Status { + case "passed": + status = v1.NewSuccessfulTestStatus() + case "failed": + status = v1.NewFailedTestStatus(step.Result.ErrorMessage, nil, nil) + case "skipped": + status = v1.NewSkippedTestStatus(step.Result.ErrorMessage) + case "undefined": + status = v1.NewTodoTestStatus(step.Result.ErrorMessage) + case "pending": + status = v1.NewTodoTestStatus(step.Result.ErrorMessage) + case "ambiguous": + status = v1.NewFailedTestStatus(step.Result.ErrorMessage, nil, nil) + default: + return nil, errors.NewInputError( + "Unexpected status %v for cucumber step %v", + step.Result.Status, + step, + ) + } + } + } + + element := element + location := v1.Location{File: feature.URI, Line: &element.Line} + attempt := v1.TestAttempt{ + Duration: &duration, + Status: status, + Meta: map[string]any{"tags": element.Tags}, + } + + lineage := []string{feature.Name, element.Name} + tests = append( + tests, + v1.Test{ + Name: strings.Join(lineage, " > "), + Lineage: lineage, + Location: &location, + Attempt: attempt, + PastAttempts: nil, + }, + ) + } + } + + return v1.NewTestResults( + v1.JavaScriptCucumberFramework, + tests, + otherErrors, + ), nil +} diff --git a/internal/captain/parsing/javascript_cucumber_json_parser_test.go b/internal/captain/parsing/javascript_cucumber_json_parser_test.go new file mode 100644 index 0000000..f5285c8 --- /dev/null +++ b/internal/captain/parsing/javascript_cucumber_json_parser_test.go @@ -0,0 +1,62 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptCucumberJSONParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/cucumber-js.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptCucumberJSONParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("errors on malformed JSON", func() { + testResults, err := parsing.JavaScriptCucumberJSONParser{}.Parse(strings.NewReader(`{`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as JSON")) + Expect(testResults).To(BeNil()) + }) + + It("errors on JSON that doesn't look like Cucumber", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.JavaScriptCucumberJSONParser{}.Parse(strings.NewReader(`[]`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No test results were found in the JSON")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptCucumberJSONParser{}.Parse(strings.NewReader(` + [ + { + "id": "rule-sample", + "uri": "features/rule.feature", + "keyword": "Feature", + "name": "Rule Sample", + "description": "", + "line": 2 + } + ]`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Found features, but no results in the JSON")) + Expect(testResults).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/javascript_cypress_parser.go b/internal/captain/parsing/javascript_cypress_parser.go new file mode 100644 index 0000000..cc56f2f --- /dev/null +++ b/internal/captain/parsing/javascript_cypress_parser.go @@ -0,0 +1,186 @@ +package parsing + +import ( + "encoding/xml" + "fmt" + "io" + "math" + "regexp" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +// Parses https://github.com/michaelleeallen/mocha-junit-reporter +type JavaScriptCypressParser struct{} + +type JavaScriptCypressFailure struct { + Type *string `xml:"type,attr"` + Message *string `xml:"message,attr"` + Contents string `xml:",cdata"` +} + +type JavaScriptCypressSkipped struct{} + +type JavaScriptCypressTestCase struct { + ClassName string `xml:"classname,attr"` + Error *JavaScriptCypressFailure `xml:"error"` + Failure *JavaScriptCypressFailure `xml:"failure"` + Name string `xml:"name,attr"` + Skipped *JavaScriptCypressSkipped `xml:"skipped"` + SystemErr *string `xml:"system-err"` + SystemOut *string `xml:"system-out"` + Time float64 `xml:"time,attr"` + XMLName xml.Name `xml:"testcase"` +} + +type JavaScriptCypressProperty struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} + +type JavaScriptCypressTestSuite struct { + Errors int `xml:"errors,attr"` + Failures int `xml:"failures,attr"` + File *string `xml:"file,attr"` + Name string `xml:"name,attr"` + Skipped int `xml:"skipped,attr"` + TestCases []JavaScriptCypressTestCase `xml:"testcase"` + Properties []JavaScriptCypressProperty `xml:"properties>property"` + Tests int `xml:"tests,attr"` + Time float64 `xml:"time,attr"` + Timestamp string `xml:"timestamp,attr"` + XMLName xml.Name `xml:"testsuite"` +} + +type JavaScriptCypressTestResults struct { + Errors int `xml:"errors,attr"` + Failures int `xml:"failures,attr"` + Skipped int `xml:"skipped,attr"` + Tests *int `xml:"tests,attr"` + TestSuites []JavaScriptCypressTestSuite `xml:"testsuite"` + XMLName xml.Name `xml:"testsuites"` +} + +var javaScriptCypressNewlineRegexp = regexp.MustCompile(`\r?\n`) + +func (p JavaScriptCypressParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testResults JavaScriptCypressTestResults + + if err := xml.NewDecoder(data).Decode(&testResults); err != nil { + return nil, errors.NewInputError("Unable to parse test results as XML: %s", err) + } + if testResults.Tests == nil { + return nil, errors.NewInputError("No tests count was found in the XML") + } + if len(testResults.TestSuites) == 0 { + return nil, errors.NewInputError("The test suites in the XML do not appear to match Cypress XML") + } + if testResults.TestSuites[0].File == nil { + return nil, errors.NewInputError("The test suites in the XML do not appear to match Cypress XML") + } + firstFile := testResults.TestSuites[0].File + if !strings.Contains(*firstFile, ".cy.") && !strings.Contains(*firstFile, "cypress/") { + return nil, errors.NewInputError("The file does not look like a Cypress file: %q", *firstFile) + } + + tests := make([]v1.Test, 0) + var currentFile *string + for _, testSuite := range testResults.TestSuites { + if testSuite.File != nil { + currentFile = testSuite.File + + if !strings.Contains(*currentFile, ".cy.") && !strings.Contains(*currentFile, "cypress/") { + return nil, errors.NewInputError("The file does not look like a Cypress file: %q", *currentFile) + } + } + + var properties map[string]any + if len(testSuite.Properties) > 0 { + properties = make(map[string]any) + } + for _, property := range testSuite.Properties { + properties[property.Name] = property.Value + } + + for _, testCase := range testSuite.TestCases { + // The mocha junit reporter library allows switching these + // We want the one that has the entire description (contains the short description) + // e.g. classname="Some Tests with some context it passes" name="it passes" + // We'd want the classname in the above case. Classname contains name, but name doesn't contain classname + var name string + switch { + case strings.Contains(testCase.Name, testCase.ClassName) && strings.Contains(testCase.ClassName, testCase.Name): + name = testCase.Name + case !strings.Contains(testCase.Name, testCase.ClassName) && strings.Contains(testCase.ClassName, testCase.Name): + name = testCase.ClassName + case strings.Contains(testCase.Name, testCase.ClassName) && !strings.Contains(testCase.ClassName, testCase.Name): + name = testCase.Name + case !strings.Contains(testCase.Name, testCase.ClassName) && !strings.Contains(testCase.ClassName, testCase.Name): + name = fmt.Sprintf("%s %s", testCase.ClassName, testCase.Name) + default: + return nil, errors.NewInternalError("Unreachable: reached default case of exhaustive switch statement") + } + + duration := time.Duration(math.Round(testCase.Time * float64(time.Second))) + + var status v1.TestStatus + switch { + case testCase.Failure != nil: + failureMessage := testCase.Failure.Message + failureException := testCase.Failure.Type + + lines := javaScriptCypressNewlineRegexp.Split(testCase.Failure.Contents, -1) + var backtrace []string + for _, line := range lines { + if failureException != nil && + failureMessage != nil && + line == fmt.Sprintf("%s: %s", *failureException, *failureMessage) { + continue + } + + backtrace = append(backtrace, strings.TrimSpace(line)) + } + + status = v1.NewFailedTestStatus(failureMessage, failureException, backtrace) + case testCase.Skipped != nil: + status = v1.NewSkippedTestStatus(nil) + case testCase.Error != nil: + return nil, errors.NewInputError( + "Unexpected element in %q, mocha-junit-reporter does not emit these.", + name, + ) + default: + status = v1.NewSuccessfulTestStatus() + } + + var location *v1.Location + if currentFile != nil { + location = &v1.Location{File: *currentFile} + } + + tests = append( + tests, + v1.Test{ + Name: name, + Location: location, + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: properties, + Status: status, + Stderr: testCase.SystemErr, + Stdout: testCase.SystemOut, + }, + }, + ) + } + } + + return v1.NewTestResults( + v1.JavaScriptCypressFramework, + tests, + nil, + ), nil +} diff --git a/internal/captain/parsing/javascript_cypress_parser_test.go b/internal/captain/parsing/javascript_cypress_parser_test.go new file mode 100644 index 0000000..c438129 --- /dev/null +++ b/internal/captain/parsing/javascript_cypress_parser_test.go @@ -0,0 +1,335 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + "time" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptCypressParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/cypress.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptCypressParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("errors on malformed XML", func() { + testResults, err := parsing.JavaScriptCypressParser{}.Parse(strings.NewReader(``)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as XML")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptCypressParser{}.Parse(strings.NewReader(``)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No tests count was found in the XML")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptCypressParser{}.Parse( + strings.NewReader(``), + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring("The test suites in the XML do not appear to match Cypress XML"), + ) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptCypressParser{}.Parse( + strings.NewReader(``), + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring("The test suites in the XML do not appear to match Cypress XML"), + ) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptCypressParser{}.Parse( + strings.NewReader(``), + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring("The file does not look like a Cypress file: \"not/right.js\""), + ) + Expect(testResults).To(BeNil()) + }) + + It("parses files properly", func() { + fixture, err := os.Open("../../test/fixtures/cypress.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptCypressParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + nameOne := "example to-do app one displays two todo items by default" + nameTwo := "example to-do app two can add new todo items" + for _, test := range testResults.Tests { + switch test.Name { + case nameOne: + Expect(test.Location.File).To(Equal("cypress/e2e/todo-one.cy.js")) + case nameTwo: + Expect(test.Location.File).To(Equal("cypress/e2e/todo-two.cy.js")) + default: + continue + } + return + } + + Fail("Unreachable") + }) + + It("parses successful tests", func() { + fixture, err := os.Open("../../test/fixtures/cypress.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptCypressParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + name := "displays two todo items by default" + for _, test := range testResults.Tests { + if test.Name != name { + continue + } + + Expect(test.Attempt.Status).To(Equal(v1.NewSuccessfulTestStatus())) + Expect(*test.Attempt.Duration).To(Equal(time.Duration(490000000))) + + return + } + + Fail("Unreachable") + }) + + It("parses tests which failed via an assertion", func() { + fixture, err := os.Open("../../test/fixtures/cypress.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptCypressParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + name := "example to-do app one displays two todo items by default" + for _, test := range testResults.Tests { + if test.Name != name { + continue + } + + message := "Timed out retrying after 4000ms: expected '
  • ' to have text 'Walk the cat', but" + + " the text was 'Walk the dog'" + class := "AssertionError" + backtrace := []string{ + "at Context.eval (webpack:///./cypress/e2e/todo-one.cy.js:59:35)", + "", + "+ expected - actual", + "", + "-'Walk the dog'", + "+'Walk the cat'", + "", + } + Expect(test.Attempt.Status).To(Equal(v1.NewFailedTestStatus(&message, &class, backtrace))) + + return + } + + Fail("Unreachable") + }) + + It("parses tests which failed via a custom error", func() { + fixture, err := os.Open("../../test/fixtures/cypress.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptCypressParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + name := "example to-do app one can add new todo items" + for _, test := range testResults.Tests { + if test.Name != name { + continue + } + + message := "uh oh something broke" + class := "Error" + backtrace := []string{"at Context.eval (webpack:///./cypress/e2e/todo-one.cy.js:73:10)"} + Expect(test.Attempt.Status).To(Equal(v1.NewFailedTestStatus(&message, &class, backtrace))) + + return + } + + Fail("Unreachable") + }) + + It("parses tests which failed via a custom error", func() { + fixture, err := os.Open("../../test/fixtures/cypress.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptCypressParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + name := "example to-do app one with a checked task can filter for uncompleted tasks" + for _, test := range testResults.Tests { + if test.Name != name { + continue + } + + message := "not a custom error" + class := "Error" + backtrace := []string{ + "at $Cy.fail (https://example.cypress.io/__cypress/runner/cypress_runner.js:157093:13)", + "at runnable.fn (https://example.cypress.io/__cypress/runner/cypress_runner.js:157707:19)", + "at callFn (https://example.cypress.io/__cypress/runner/cypress_runner.js:107910:21)", + "at ../driver/node_modules/mocha/lib/runnable.js.Runnable.run" + + " (https://example.cypress.io/__cypress/runner/cypress_runner.js:107897:7)", + "at (https://example.cypress.io/__cypress/runner/cypress_runner.js:164958:30)", + "at PassThroughHandlerContext.finallyHandler" + + " (https://example.cypress.io/__cypress/runner/cypress_runner.js:7872:23)", + "at PassThroughHandlerContext.tryCatcher" + + " (https://example.cypress.io/__cypress/runner/cypress_runner.js:11318:23)", + "at Promise._settlePromiseFromHandler (https://example.cypress.io/__cypress/runner/cypress_runner.js:9253:31)", + "at Promise._settlePromise (https://example.cypress.io/__cypress/runner/cypress_runner.js:9310:18)", + "at Promise._settlePromise0 (https://example.cypress.io/__cypress/runner/cypress_runner.js:9355:10)", + "at Promise._settlePromises (https://example.cypress.io/__cypress/runner/cypress_runner.js:9435:18)", + "at _drainQueueStep (https://example.cypress.io/__cypress/runner/cypress_runner.js:6025:12)", + "at _drainQueue (https://example.cypress.io/__cypress/runner/cypress_runner.js:6018:9)", + "at ../../node_modules/bluebird/js/release/async.js.Async._drainQueues" + + " (https://example.cypress.io/__cypress/runner/cypress_runner.js:6034:5)", + "at Async.drainQueues (https://example.cypress.io/__cypress/runner/cypress_runner.js:5904:14)", + } + Expect(test.Attempt.Status).To(Equal(v1.NewFailedTestStatus(&message, &class, backtrace))) + + return + } + + Fail("Unreachable") + }) + + It("errors when presented with an element", func() { + testResults, err := parsing.JavaScriptCypressParser{}.Parse(strings.NewReader( + ` + + + + + + + + `, + )) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring( + `Unexpected element in "some test name", mocha-junit-reporter does not emit these.`, + )) + Expect(testResults).To(BeNil()) + }) + + It("errors when the file name doesn't look like Cypress", func() { + testResults, err := parsing.JavaScriptCypressParser{}.Parse(strings.NewReader( + ` + + + + + + + + `, + )) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring( + `The file does not look like a Cypress file: "e2e/some.test.js"`, + )) + Expect(testResults).To(BeNil()) + }) + + It("calculates the correct name when the classname contains the name", func() { + testResults, err := parsing.JavaScriptCypressParser{}.Parse(strings.NewReader( + ` + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Name).To(Equal("prefix some test name")) + }) + + It("calculates the correct name when the name contains the classname", func() { + testResults, err := parsing.JavaScriptCypressParser{}.Parse(strings.NewReader( + ` + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Name).To(Equal("prefix some test name")) + }) + + It("calculates the correct name when the name is the same as classname", func() { + testResults, err := parsing.JavaScriptCypressParser{}.Parse(strings.NewReader( + ` + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Name).To(Equal("prefix some test name")) + }) + + It("calculates the correct name when the name is entirely different from the classname", func() { + testResults, err := parsing.JavaScriptCypressParser{}.Parse(strings.NewReader( + ` + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Name).To(Equal("prefix some test name")) + }) + }) +}) diff --git a/internal/captain/parsing/javascript_jest_parser.go b/internal/captain/parsing/javascript_jest_parser.go new file mode 100644 index 0000000..bf6f909 --- /dev/null +++ b/internal/captain/parsing/javascript_jest_parser.go @@ -0,0 +1,258 @@ +package parsing + +import ( + "encoding/json" + "fmt" + "io" + "regexp" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JavaScriptJestParser struct{} + +type JavaScriptJestSerializableError struct { + Code *int `json:"code"` + Message string `json:"message"` + Stack *string `json:"stack"` + Type *string `json:"type"` +} + +type JavaScriptJestUncheckedSnapshot struct { + FilePath string `json:"filePath"` + Keys []string `json:"keys"` +} + +type JavaScriptJestSnapshot struct { + Added int `json:"added"` + DidUpdate bool `json:"didUpdate"` + Failure bool `json:"failure"` + FilesAdded int `json:"filesAdded"` + FilesRemoved int `json:"filesRemoved"` + FilesRemovedList []string `json:"filesRemovedList"` + FilesUnmatched int `json:"filesUnmatched"` + FilesUpdated int `json:"filesUpdated"` + Matched int `json:"matched"` + Total int `json:"total"` + Unchecked int `json:"unchecked"` + UncheckedKeysByFile []JavaScriptJestUncheckedSnapshot `json:"uncheckedKeysByFile"` + Unmatched int `json:"unmatched"` + Updated int `json:"updated"` +} + +type JavaScriptJestCallsite struct { + Column int `json:"column"` + Line int `json:"line"` +} + +// https://github.com/facebook/jest/blob/6fc1860a34ea64a7c3360580e2874c94a5c8fc83/packages/jest-test-result/src/types.ts#LL47 +// https://github.com/facebook/jest/blob/6fc1860a34ea64a7c3360580e2874c94a5c8fc83/packages/jest-types/src/TestResult.ts#L16 +// https://github.com/facebook/jest/blob/6fc1860a34ea64a7c3360580e2874c94a5c8fc83/packages/jest-types/src/TestResult.ts#LL8C15-L8C80 +// Same deal here with things not in the spec, but in the output +type JavaScriptJestAssertionResult struct { + AncestorTitles []string `json:"ancestorTitles"` + Duration *int `json:"duration"` + FailureMessages []string `json:"failureMessages"` + FullName string `json:"fullName"` + Location *JavaScriptJestCallsite `json:"location"` + Status string `json:"status"` + Title string `json:"title"` + + // Not in the spec + Invocations *int `json:"invocations"` + NumPassingAsserts *int `json:"numPassingAsserts"` + RetryReasons []string `json:"retryReasons"` +} + +// https://github.com/facebook/jest/blob/6fc1860a34ea64a7c3360580e2874c94a5c8fc83/packages/jest-test-result/src/types.ts#L124 +type JavaScriptJestTestResult struct { + AssertionResults []JavaScriptJestAssertionResult `json:"assertionResults"` + EndTime int `json:"endTime"` + Message string `json:"message"` + Name string `json:"name"` + StartTime int `json:"startTime"` + Status string `json:"status"` + Summary string `json:"summary"` +} + +// Per the code, this is what we should expect to see: +// https://github.com/facebook/jest/blob/6fc1860a34ea64a7c3360580e2874c94a5c8fc83/packages/jest-test-result/src/types.ts#L135 +// In actuality, the Jest code seems to dump extra details (TypeScript does structural typing, so an object that has at +// least the declared attributes will type check). +// Here, we adhere to the linked code, but optionally support the extra bits in case they decide to remove them later. +// Inline comments indicate which ones aren't in the spec. This is how they get formatted: +// https://github.com/facebook/jest/blob/6fc1860a34ea64a7c3360580e2874c94a5c8fc83/packages/jest-test-result/src/formatTestResults.ts#L17 +type JavaScriptJestTestResults struct { + NumFailedTests int `json:"numFailedTests"` + NumFailedTestSuites int `json:"numFailedTestSuites"` + NumPassedTests int `json:"numPassedTests"` + NumPassedTestSuites int `json:"numPassedTestSuites"` + NumPendingTests int `json:"numPendingTests"` + NumPendingTestSuites int `json:"numPendingTestSuites"` + NumRuntimeErrorTestSuites *int `json:"numRuntimeErrorTestSuites"` + NumTotalTests int `json:"numTotalTests"` + NumTotalTestSuites int `json:"numTotalTestSuites"` + Snapshot *JavaScriptJestSnapshot `json:"snapshot"` + StartTime int `json:"startTime"` + Success bool `json:"success"` + TestResults []JavaScriptJestTestResult `json:"testResults"` + WasInterrupted bool `json:"wasInterrupted"` + + // Not in the spec + NumTodoTests *int `json:"numTodoTests"` + OpenHandles []any `json:"openHandles"` + RunExecError *JavaScriptJestSerializableError `json:"runExecError"` +} + +var javaScriptJestNewlineRegexp = regexp.MustCompile(`\r?\n`) + +var javaScriptJestBacktraceSeparatorRegexp = regexp.MustCompile(`\r?\n\s{4}at`) + +func (p JavaScriptJestParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testResults JavaScriptJestTestResults + + if err := json.NewDecoder(data).Decode(&testResults); err != nil { + return nil, errors.NewInputError("Unable to parse test results as JSON: %s", err) + } + if testResults.TestResults == nil { + return nil, errors.NewInputError("No test results were found in the JSON") + } + if testResults.Snapshot == nil { + return nil, errors.NewInputError("No snapshot was found in the JSON") + } + if testResults.NumRuntimeErrorTestSuites == nil { + return nil, errors.NewInputError("No number of runtime error test suites was found in the JSON") + } + + tests := make([]v1.Test, 0) + otherErrors := make([]v1.OtherError, 0) + for _, testResult := range testResults.TestResults { + sawFailedTest := false + file := testResult.Name + + for _, assertionResult := range testResult.AssertionResults { + lineage := assertionResult.AncestorTitles + lineage = append(lineage, assertionResult.Title) + name := strings.Join(lineage, " > ") + + var line *int + var column *int + if assertionResult.Location != nil { + assertionResult := assertionResult + line = &assertionResult.Location.Line + column = &assertionResult.Location.Column + } + location := v1.Location{File: file, Line: line, Column: column} + + var duration *time.Duration + if assertionResult.Duration != nil { + transformedDuration := time.Duration(*assertionResult.Duration * int(time.Millisecond)) + duration = &transformedDuration + } + + var status v1.TestStatus + switch assertionResult.Status { + case "passed": + status = v1.NewSuccessfulTestStatus() + case "failed": + message, backtrace := p.extractFailureMetadata(assertionResult.FailureMessages) + status = v1.NewFailedTestStatus(message, nil, backtrace) + sawFailedTest = true + case "skipped": + status = v1.NewSkippedTestStatus(nil) + case "pending": + status = v1.NewPendedTestStatus(nil) + case "todo": + status = v1.NewTodoTestStatus(nil) + default: + return nil, errors.NewInputError( + "Unexpected status %q for assertion result %v", + assertionResult.Status, + assertionResult, + ) + } + + attempt := v1.TestAttempt{Duration: duration, Status: status} + + var pastAttempts []v1.TestAttempt + switch { + case assertionResult.RetryReasons != nil: + for _, retryReason := range assertionResult.RetryReasons { + message, backtrace := p.extractFailureMetadata([]string{retryReason}) + status := v1.NewFailedTestStatus(message, nil, backtrace) + pastAttempts = append(pastAttempts, v1.TestAttempt{Status: status}) + } + case assertionResult.Invocations != nil && *assertionResult.Invocations > 1: + for i := 0; i < *assertionResult.Invocations-1; i++ { + status := v1.NewFailedTestStatus(nil, nil, nil) + pastAttempts = append(pastAttempts, v1.TestAttempt{Status: status}) + } + default: + pastAttempts = nil + } + + tests = append( + tests, + v1.Test{ + Name: name, + Lineage: lineage, + Location: &location, + Attempt: attempt, + PastAttempts: pastAttempts, + }, + ) + } + + if !sawFailedTest && testResult.Status == "failed" { + if len(testResult.Name) > 0 { + otherErrors = append(otherErrors, v1.OtherError{ + Message: testResult.Message, + Location: &v1.Location{File: testResult.Name}, + }) + } else { + otherErrors = append(otherErrors, v1.OtherError{Message: testResult.Message}) + } + } + } + + if testResults.RunExecError != nil { + var backtrace []string + if testResults.RunExecError.Stack != nil { + backtrace = javaScriptJestNewlineRegexp.Split(*testResults.RunExecError.Stack, -1) + } + otherErrors = append( + otherErrors, + v1.OtherError{Message: testResults.RunExecError.Message, Backtrace: backtrace}, + ) + } + + for range testResults.OpenHandles { + otherErrors = append(otherErrors, v1.OtherError{Message: "An open handle was detected"}) + } + + return v1.NewTestResults( + v1.JavaScriptJestFramework, + tests, + otherErrors, + ), nil +} + +func (p JavaScriptJestParser) extractFailureMetadata(failureMessages []string) (*string, []string) { + var message *string + var backtrace []string + + if failureMessages != nil && failureMessages[0] != "" { + parts := javaScriptJestBacktraceSeparatorRegexp.Split(failureMessages[0], -1) + first, rest := parts[0], parts[1:] + message = &first + + for _, part := range rest { + backtrace = append(backtrace, fmt.Sprintf("at%s", part)) + } + } + + return message, backtrace +} diff --git a/internal/captain/parsing/javascript_jest_parser_test.go b/internal/captain/parsing/javascript_jest_parser_test.go new file mode 100644 index 0000000..26b4769 --- /dev/null +++ b/internal/captain/parsing/javascript_jest_parser_test.go @@ -0,0 +1,521 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + "time" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptJestParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/jest.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses empty files", func() { + testResults, err := parsing.JavaScriptVitestParser{}.Parse( + strings.NewReader(`{"testResults": [], "numRuntimeErrorTestSuites": 0, "snapshot": {}}`), + ) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults.Tests).To(HaveLen(0)) + }) + + It("errors on malformed JSON", func() { + testResults, err := parsing.JavaScriptJestParser{}.Parse(strings.NewReader(`{`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as JSON")) + Expect(testResults).To(BeNil()) + }) + + It("errors on JSON that doesn't look like Jest", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.JavaScriptJestParser{}.Parse(strings.NewReader(`{}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No test results were found in the JSON")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptJestParser{}.Parse(strings.NewReader(`{"testResults": []}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No snapshot was found in the JSON")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptJestParser{}.Parse( + strings.NewReader(`{"testResults": [], "snapshot": {}}`), + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring("No number of runtime error test suites was found in the JSON"), + ) + Expect(testResults).To(BeNil()) + }) + + It("parses a minimally failing test", func() { + zero := 0 + durationInMilliseconds := 653 + jestResults := parsing.JavaScriptJestTestResults{ + NumRuntimeErrorTestSuites: &zero, + Snapshot: &parsing.JavaScriptJestSnapshot{}, + TestResults: []parsing.JavaScriptJestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptJestAssertionResult{ + { + Duration: &durationInMilliseconds, + Status: "failed", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + expectedDuration := time.Duration(653000000) + Expect(testResults.Tests[0]).To(Equal( + v1.Test{ + Name: "title", + Lineage: []string{"title"}, + Location: &v1.Location{File: "/some/path/to/name/of/file.js"}, + Attempt: v1.TestAttempt{ + Duration: &expectedDuration, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + )) + }) + + It("parses a maximally failing test", func() { + zero := 0 + durationInMilliseconds := 653 + invocations := 3 + jestResults := parsing.JavaScriptJestTestResults{ + NumRuntimeErrorTestSuites: &zero, + Snapshot: &parsing.JavaScriptJestSnapshot{}, + TestResults: []parsing.JavaScriptJestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptJestAssertionResult{ + { + AncestorTitles: []string{"part 1", "part 2"}, + Duration: &durationInMilliseconds, + FailureMessages: []string{"final\n\nmessage\n at stacktrace"}, + Status: "failed", + Title: "title", + Location: &parsing.JavaScriptJestCallsite{Line: 10, Column: 12}, + RetryReasons: []string{ + "first\n\nmessage\n at stacktrace\n at other trace", + "second\n\nmessage", + }, + Invocations: &invocations, + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + expectedDuration := time.Duration(653000000) + line := 10 + column := 12 + firstFailureMessage := "first\n\nmessage" + firstBacktrace := []string{"at stacktrace", "at other trace"} + secondFailureMessage := "second\n\nmessage" + finalFailureMessage := "final\n\nmessage" + finalBacktrace := []string{"at stacktrace"} + Expect(testResults.Tests[0]).To(Equal( + v1.Test{ + Name: "part 1 > part 2 > title", + Lineage: []string{"part 1", "part 2", "title"}, + Location: &v1.Location{File: "/some/path/to/name/of/file.js", Line: &line, Column: &column}, + Attempt: v1.TestAttempt{ + Duration: &expectedDuration, + Status: v1.NewFailedTestStatus(&finalFailureMessage, nil, finalBacktrace), + }, + PastAttempts: []v1.TestAttempt{ + {Status: v1.NewFailedTestStatus(&firstFailureMessage, nil, firstBacktrace)}, + {Status: v1.NewFailedTestStatus(&secondFailureMessage, nil, nil)}, + }, + }, + )) + }) + + It("does not require retry reasons to capture retries (it can use invocations)", func() { + zero := 0 + durationInMilliseconds := 653 + invocations := 3 + jestResults := parsing.JavaScriptJestTestResults{ + NumRuntimeErrorTestSuites: &zero, + Snapshot: &parsing.JavaScriptJestSnapshot{}, + TestResults: []parsing.JavaScriptJestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptJestAssertionResult{ + { + AncestorTitles: []string{"part 1", "part 2"}, + Duration: &durationInMilliseconds, + FailureMessages: []string{"final\n\nmessage\n at stacktrace"}, + Status: "failed", + Title: "title", + Location: &parsing.JavaScriptJestCallsite{Line: 10, Column: 12}, + Invocations: &invocations, + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + expectedDuration := time.Duration(653000000) + line := 10 + column := 12 + finalFailureMessage := "final\n\nmessage" + finalBacktrace := []string{"at stacktrace"} + Expect(testResults.Tests[0]).To(Equal( + v1.Test{ + Name: "part 1 > part 2 > title", + Lineage: []string{"part 1", "part 2", "title"}, + Location: &v1.Location{File: "/some/path/to/name/of/file.js", Line: &line, Column: &column}, + Attempt: v1.TestAttempt{ + Duration: &expectedDuration, + Status: v1.NewFailedTestStatus(&finalFailureMessage, nil, finalBacktrace), + }, + PastAttempts: []v1.TestAttempt{ + {Status: v1.NewFailedTestStatus(nil, nil, nil)}, + {Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + }, + )) + }) + + It("parses passed statuses", func() { + zero := 0 + durationInMilliseconds := 653 + jestResults := parsing.JavaScriptJestTestResults{ + NumRuntimeErrorTestSuites: &zero, + Snapshot: &parsing.JavaScriptJestSnapshot{}, + TestResults: []parsing.JavaScriptJestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptJestAssertionResult{ + { + Duration: &durationInMilliseconds, + Status: "passed", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Attempt.Status).To(Equal(v1.NewSuccessfulTestStatus())) + }) + + It("parses todo statuses", func() { + zero := 0 + jestResults := parsing.JavaScriptJestTestResults{ + NumRuntimeErrorTestSuites: &zero, + Snapshot: &parsing.JavaScriptJestSnapshot{}, + TestResults: []parsing.JavaScriptJestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptJestAssertionResult{ + { + Status: "todo", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Attempt.Duration).To(BeNil()) + Expect(testResults.Tests[0].Attempt.Status).To(Equal(v1.NewTodoTestStatus(nil))) + }) + + It("parses skipped statuses", func() { + zero := 0 + jestResults := parsing.JavaScriptJestTestResults{ + NumRuntimeErrorTestSuites: &zero, + Snapshot: &parsing.JavaScriptJestSnapshot{}, + TestResults: []parsing.JavaScriptJestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptJestAssertionResult{ + { + Status: "skipped", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Attempt.Status).To(Equal(v1.NewSkippedTestStatus(nil))) + }) + + It("parses pending statuses", func() { + zero := 0 + jestResults := parsing.JavaScriptJestTestResults{ + NumRuntimeErrorTestSuites: &zero, + Snapshot: &parsing.JavaScriptJestSnapshot{}, + TestResults: []parsing.JavaScriptJestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptJestAssertionResult{ + { + Status: "pending", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Attempt.Status).To(Equal(v1.NewPendedTestStatus(nil))) + }) + + It("errors on other statuses", func() { + zero := 0 + jestResults := parsing.JavaScriptJestTestResults{ + NumRuntimeErrorTestSuites: &zero, + Snapshot: &parsing.JavaScriptJestSnapshot{}, + TestResults: []parsing.JavaScriptJestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptJestAssertionResult{ + { + Status: "wat", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unexpected status \"wat\"")) + Expect(testResults).To(BeNil()) + }) + + It("parses an other error when a whole test result fails", func() { + zero := 0 + jestResults := parsing.JavaScriptJestTestResults{ + NumRuntimeErrorTestSuites: &zero, + Snapshot: &parsing.JavaScriptJestSnapshot{}, + TestResults: []parsing.JavaScriptJestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "failed", + Message: "the reason it failed", + AssertionResults: []parsing.JavaScriptJestAssertionResult{ + { + Status: "passed", + Title: "title", + }, + }, + }, + { + Name: "", + Status: "failed", + Message: "no name", + AssertionResults: []parsing.JavaScriptJestAssertionResult{ + { + Status: "passed", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.OtherErrors[0]).To(Equal( + v1.OtherError{ + Location: &v1.Location{File: "/some/path/to/name/of/file.js"}, + Message: "the reason it failed", + }, + )) + Expect(testResults.OtherErrors[1]).To(Equal(v1.OtherError{Message: "no name"})) + Expect(testResults.Tests[0]).NotTo(BeNil()) + }) + + It("does not parse an other error when a whole test result fails due to a failing test", func() { + zero := 0 + jestResults := parsing.JavaScriptJestTestResults{ + NumRuntimeErrorTestSuites: &zero, + Snapshot: &parsing.JavaScriptJestSnapshot{}, + TestResults: []parsing.JavaScriptJestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "failed", + Message: "the reason it failed", + AssertionResults: []parsing.JavaScriptJestAssertionResult{ + { + Status: "failed", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.OtherErrors).To(HaveLen(0)) + Expect(testResults.Tests[0]).NotTo(BeNil()) + }) + + It("parses an other error when there is a run exec error", func() { + zero := 0 + stack := "the stacktrace\nwith multiple lines" + jestResults := parsing.JavaScriptJestTestResults{ + NumRuntimeErrorTestSuites: &zero, + Snapshot: &parsing.JavaScriptJestSnapshot{}, + TestResults: []parsing.JavaScriptJestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptJestAssertionResult{ + { + Status: "passed", + Title: "title", + }, + }, + }, + }, + RunExecError: &parsing.JavaScriptJestSerializableError{ + Message: "the reason it failed", + Stack: &stack, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.OtherErrors[0]).To(Equal( + v1.OtherError{ + Message: "the reason it failed", + Backtrace: []string{"the stacktrace", "with multiple lines"}, + }, + )) + Expect(testResults.Tests[0]).NotTo(BeNil()) + }) + + It("parses an other error when there are open handles", func() { + zero := 0 + jestResults := parsing.JavaScriptJestTestResults{ + NumRuntimeErrorTestSuites: &zero, + Snapshot: &parsing.JavaScriptJestSnapshot{}, + TestResults: []parsing.JavaScriptJestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptJestAssertionResult{ + { + Status: "passed", + Title: "title", + }, + }, + }, + }, + OpenHandles: []any{struct{}{}}, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.OtherErrors[0]).To(Equal( + v1.OtherError{Message: "An open handle was detected"}, + )) + Expect(testResults.Tests[0]).NotTo(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/javascript_karma_parser.go b/internal/captain/parsing/javascript_karma_parser.go new file mode 100644 index 0000000..3971175 --- /dev/null +++ b/internal/captain/parsing/javascript_karma_parser.go @@ -0,0 +1,153 @@ +package parsing + +import ( + "encoding/json" + "io" + "regexp" + "time" + + "github.com/mileusna/useragent" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +// Parses https://github.com/douglasduteil/karma-json-reporter +type JavaScriptKarmaParser struct{} + +type JavaScriptKarmaTestResults struct { + Browsers map[string]*JavaScriptKarmaBrowser `json:"browsers"` + Result map[string][]JavaScriptKarmaTestCase `json:"result"` + Summary *JavaScriptKarmaSummary `json:"summary"` +} + +type JavaScriptKarmaBrowser struct { + ID string `json:"id"` + FullName string `json:"fullName"` + Name string `json:"name"` + State string `json:"state"` +} + +type JavaScriptKarmaTestCase struct { + FullName string `json:"fullName"` + Description string `json:"description"` + ID string `json:"id"` + Log []string `json:"log"` + Skipped bool `json:"skipped"` + Disabled bool `json:"disabled"` + Pending bool `json:"pending"` + Success bool `json:"success"` + Suite []string `json:"suite"` + Time int `json:"time"` + ExecutedExpectationsCount int `json:"executedExpectationsCount"` + PassedExpectations []*JavaScriptKarmaExpectation `json:"passedExpectations"` +} + +type JavaScriptKarmaExpectation struct { + MatcherName string `json:"matcherName"` + Message string `json:"message"` + Passed bool `json:"passed"` + Stack string `json:"stack"` +} + +type JavaScriptKarmaSummary struct { + Disconnected bool `json:"disconnected"` + Error bool `json:"error"` + ExitCode int `json:"exitCode"` + Failed int `json:"failed"` + Skipped int `json:"skipped"` + Success int `json:"success"` +} + +var javaScriptKarmaBacktraceSeparatorRegexp = regexp.MustCompile(`\r?\n *`) + +func (p JavaScriptKarmaParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testResults JavaScriptKarmaTestResults + + if err := json.NewDecoder(data).Decode(&testResults); err != nil { + return nil, errors.NewInputError("Unable to parse test results as JSON: %s", err) + } + + if testResults.Browsers == nil { + return nil, errors.NewInputError("The file does not look like a Karma file") + } + if testResults.Result == nil { + return nil, errors.NewInputError("The file does not look like a Karma file") + } + if testResults.Summary == nil { + return nil, errors.NewInputError("The file does not look like a Karma file") + } + + tests := make([]v1.Test, 0) + var currentBrowser *JavaScriptKarmaBrowser + for browserID, testCases := range testResults.Result { + currentBrowser = testResults.Browsers[browserID] + if currentBrowser == nil { + return nil, errors.NewInputError("The file does not look like a Karma file") + } + + for _, testCase := range testCases { + id := testCase.ID + duration := time.Duration(testCase.Time * int(time.Millisecond)) + var status v1.TestStatus + switch { + case testCase.Skipped: + status = v1.NewSkippedTestStatus(nil) + case testCase.Pending: + status = v1.NewPendedTestStatus(nil) + case testCase.Disabled: + status = v1.NewSkippedTestStatus(nil) + case testCase.Success: + status = v1.NewSuccessfulTestStatus() + default: + var errorMessage string + var backtrace []string + + if len(testCase.Log) > 0 { + firstLog := testCase.Log[0] + firstLogLines := javaScriptKarmaBacktraceSeparatorRegexp.Split(firstLog, -1) + if len(firstLogLines) > 0 { + errorMessage = firstLogLines[0] + backtrace = firstLogLines[1:] + } + status = v1.NewFailedTestStatus(&errorMessage, nil, backtrace) + } + } + + ua := useragent.Parse(currentBrowser.FullName) + browserName := ua.Name + " " + ua.OS + + tests = append( + tests, + v1.Test{ + ID: &id, + Name: testCase.FullName, + Scope: &browserName, + Lineage: append(testCase.Suite, testCase.Description), + Attempt: v1.TestAttempt{ + Duration: &duration, + Status: status, + Meta: map[string]any{ + "browserName": browserName, + "browserFullName": currentBrowser.FullName, + "browserId": currentBrowser.ID, + }, + }, + }, + ) + } + } + + otherErrors := make([]v1.OtherError, 0) + if testResults.Summary.Failed == 0 && testResults.Summary.Error { + otherErrors = append(otherErrors, v1.OtherError{ + Message: "An unknown error occurred", + }) + } + + return v1.NewTestResults( + v1.JavaScriptKarmaFramework, + tests, + otherErrors, + ), nil +} diff --git a/internal/captain/parsing/javascript_karma_parser_test.go b/internal/captain/parsing/javascript_karma_parser_test.go new file mode 100644 index 0000000..9209a00 --- /dev/null +++ b/internal/captain/parsing/javascript_karma_parser_test.go @@ -0,0 +1,176 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptKarmaParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/karma.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptKarmaParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("errors on JSON that doesn't look like Karma", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.JavaScriptKarmaParser{}.Parse(strings.NewReader(`{ + "result": {}, + "summary": {} + } + `)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("The file does not look like a Karma file")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptKarmaParser{}.Parse(strings.NewReader(`{ + "browsers": {}, + "summary": {} + } + `)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("The file does not look like a Karma file")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptKarmaParser{}.Parse(strings.NewReader(`{ + "browsers": {}, + "result": {} + } + `)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("The file does not look like a Karma file")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptKarmaParser{}.Parse(strings.NewReader(`{ + "browsers": { + }, + "result": { + "87843490": [ + { + "fullName": "Some test context is a contextual passing test", + "description": "is a contextual passing test", + "id": "spec3", + "log": [], + "skipped": false, + "disabled": false, + "pending": false, + "success": true, + "suite": ["Some test", "context"], + "time": 1, + "executedExpectationsCount": 1, + "passedExpectations": [ + { + "matcherName": "toBe", + "message": "Passed.", + "stack": "", + "passed": true + } + ], + "properties": null + } + ] + }, + "summary": { + "success": 2, + "failed": 1, + "skipped": 1, + "error": true, + "disconnected": false, + "exitCode": 1 + } + } + `)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("The file does not look like a Karma file")) + Expect(testResults).To(BeNil()) + }) + + It("puts a generic other error in place when there are no errors but the suite failed", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.JavaScriptKarmaParser{}.Parse(strings.NewReader(`{ + "browsers": { + "87843490": { + "id": "87843490", + "fullName": "Mozilla/5.0", + "name": "Chrome 109.0.0.0 (Mac OS 10.15.7)", + "state": "DISCONNECTED", + "lastResult": { + "startTime": 1677015045628, + "total": 4, + "success": 2, + "failed": 1, + "skipped": 1, + "totalTime": 6, + "netTime": 2, + "error": true, + "disconnected": false + }, + "disconnectsCount": 0, + "noActivityTimeout": 30000, + "disconnectDelay": 2000 + } + }, + "result": { + "87843490": [ + { + "fullName": "Some test context is a contextual passing test", + "description": "is a contextual passing test", + "id": "spec3", + "log": [], + "skipped": false, + "disabled": false, + "pending": false, + "success": true, + "suite": ["Some test", "context"], + "time": 1, + "executedExpectationsCount": 1, + "passedExpectations": [ + { + "matcherName": "toBe", + "message": "Passed.", + "stack": "", + "passed": true + } + ], + "properties": null + } + ] + }, + "summary": { + "success": 1, + "failed": 0, + "skipped": 0, + "error": true, + "disconnected": false, + "exitCode": 1 + } + } + `)) + Expect(err).ToNot(HaveOccurred()) + + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + }) +}) diff --git a/internal/captain/parsing/javascript_mocha_parser.go b/internal/captain/parsing/javascript_mocha_parser.go new file mode 100644 index 0000000..5b49a7b --- /dev/null +++ b/internal/captain/parsing/javascript_mocha_parser.go @@ -0,0 +1,143 @@ +package parsing + +import ( + "encoding/json" + "fmt" + "io" + "regexp" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JavaScriptMochaParser struct{} + +type JavaScriptMochaStats struct { + Duration int `json:"duration"` + End string `json:"end"` + Failures int `json:"failures"` + Passes *int `json:"passes,omitempty"` + Pending int `json:"pending"` + Start string `json:"start"` + Suites *int `json:"suites,omitempty"` + Tests int `json:"tests"` +} + +type JavaScriptMochaError struct { + Code *string `json:"code,omitempty"` + GeneratedMessage *bool `json:"generatedMessage,omitempty"` + Message string `json:"message"` + Name *string `json:"name,omitempty"` + Operator *string `json:"operator,omitempty"` + Stack string `json:"stack"` +} + +type JavaScriptMochaTest struct { + Title string `json:"title"` + FullTitle string `json:"fullTitle"` + File string `json:"file"` + Duration int `json:"duration"` + CurrentRetry int `json:"currentRetry"` + Speed *string `json:"speed"` + Err *JavaScriptMochaError `json:"err"` +} + +type JavaScriptMochaTestResults struct { + Stats *JavaScriptMochaStats `json:"stats"` + Tests []JavaScriptMochaTest `json:"tests"` + Pending []JavaScriptMochaTest `json:"pending"` + Failures []JavaScriptMochaTest `json:"failures"` + Passes []JavaScriptMochaTest `json:"passes"` +} + +var javaScriptMochaBacktraceSeparatorRegexp = regexp.MustCompile(`\r?\n\s{4}at`) + +func (p JavaScriptMochaParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testResults JavaScriptMochaTestResults + + if err := json.NewDecoder(data).Decode(&testResults); err != nil { + return nil, errors.NewInputError("Unable to parse test results as JSON: %s", err) + } + if testResults.Stats == nil || testResults.Stats.Passes == nil || testResults.Stats.Suites == nil { + return nil, errors.NewInputError("No stats were found in the JSON") + } + if testResults.Tests == nil { + return nil, errors.NewInputError("No tests were found in the JSON") + } + + tests := make([]v1.Test, 0) + for _, mochaTest := range testResults.Passes { + tests = append(tests, p.successfulTestFrom(mochaTest)) + } + for _, mochaTest := range testResults.Pending { + tests = append(tests, p.pendedTestFrom(mochaTest)) + } + for _, mochaTest := range testResults.Failures { + tests = append(tests, p.failedTestFrom(mochaTest)) + } + if len(tests) != len(testResults.Tests) { + return nil, errors.NewInputError("The mocha JSON has inconsistently defined passes, pending, failures, and tests") + } + + return v1.NewTestResults( + v1.JavaScriptMochaFramework, + tests, + nil, + ), nil +} + +func (p JavaScriptMochaParser) successfulTestFrom(mochaTest JavaScriptMochaTest) v1.Test { + test := p.testFrom(mochaTest) + test.Attempt.Status = v1.NewSuccessfulTestStatus() + return test +} + +func (p JavaScriptMochaParser) failedTestFrom(mochaTest JavaScriptMochaTest) v1.Test { + var message *string + var exception *string + var backtrace []string + + if mochaTest.Err != nil { + message = &mochaTest.Err.Message + exception = mochaTest.Err.Name + + stackParts := javaScriptMochaBacktraceSeparatorRegexp.Split(mochaTest.Err.Stack, -1)[1:] + for _, part := range stackParts { + backtrace = append(backtrace, fmt.Sprintf("at%s", part)) + } + } + + test := p.testFrom(mochaTest) + test.Attempt.Status = v1.NewFailedTestStatus(message, exception, backtrace) + return test +} + +func (p JavaScriptMochaParser) pendedTestFrom(mochaTest JavaScriptMochaTest) v1.Test { + test := p.testFrom(mochaTest) + test.Attempt.Status = v1.NewPendedTestStatus(nil) + return test +} + +func (p JavaScriptMochaParser) testFrom(mochaTest JavaScriptMochaTest) v1.Test { + lineage := []string{strings.TrimSuffix(mochaTest.FullTitle, fmt.Sprintf(" %v", mochaTest.Title))} + if mochaTest.FullTitle != mochaTest.Title { + lineage = append(lineage, mochaTest.Title) + } + + duration := time.Duration(mochaTest.Duration * int(time.Millisecond)) + + pastAttempts := make([]v1.TestAttempt, mochaTest.CurrentRetry) + for i := range pastAttempts { + pastAttempts[i] = v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)} + } + + return v1.Test{ + Name: mochaTest.FullTitle, + Lineage: lineage, + Location: &v1.Location{File: mochaTest.File}, + Attempt: v1.TestAttempt{Duration: &duration}, + PastAttempts: pastAttempts, + } +} diff --git a/internal/captain/parsing/javascript_mocha_parser_test.go b/internal/captain/parsing/javascript_mocha_parser_test.go new file mode 100644 index 0000000..4dd2a89 --- /dev/null +++ b/internal/captain/parsing/javascript_mocha_parser_test.go @@ -0,0 +1,87 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptMochaParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/mocha.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptMochaParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("errors on malformed JSON", func() { + testResults, err := parsing.JavaScriptMochaParser{}.Parse(strings.NewReader(`{`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as JSON")) + Expect(testResults).To(BeNil()) + }) + + It("errors on JSON that doesn't look like Mocha", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.JavaScriptMochaParser{}.Parse(strings.NewReader(`{}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No stats were found in the JSON")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptMochaParser{}.Parse(strings.NewReader(`{"stats": {}}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No stats were found in the JSON")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptMochaParser{}.Parse( + strings.NewReader(`{ "stats": { "suites": 1, "passes": 1 } }`), + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No tests were found in the JSON")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptMochaParser{}.Parse( + strings.NewReader(`{ "stats": { "suites": 1, "passes": 1 }, "tests": [] }`), + ) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + }) + + It("errors when the Mocha JSON has inconsistent results", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.JavaScriptMochaParser{}.Parse( + strings.NewReader(` + { + "stats": { "suites": 1, "passes": 1 }, + "tests": [{}, {}, {}, {}], + "passes": [{}], + "failures": [{}], + "pending": [{}] + } + `), + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring("The mocha JSON has inconsistently defined passes, pending, failures, and tests"), + ) + Expect(testResults).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/javascript_playwright_parser.go b/internal/captain/parsing/javascript_playwright_parser.go new file mode 100644 index 0000000..e98247a --- /dev/null +++ b/internal/captain/parsing/javascript_playwright_parser.go @@ -0,0 +1,354 @@ +package parsing + +import ( + "encoding/json" + "fmt" + "io" + "regexp" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JavaScriptPlaywrightParser struct{} + +// empty as we don't need anything from this that isn't already in the suite +// see https://github.com/microsoft/playwright/blob/e7088cc68573db2d7d83e2a184da16ba3f15a264/packages/playwright-test/types/testReporter.d.ts#L454-L467 +type JavaScriptPlaywrightConfig struct{} + +type JavaScriptPlaywrightTestError struct { + Message *string `json:"message"` + Stack *string `json:"stack"` + Value *string `json:"value"` +} + +type JavaScriptPlaywrightReportError struct { + Message *string `json:"message"` + Stack *string `json:"stack"` + Value *string `json:"value"` +} + +type JavaScriptPlaywrightAttachment struct { + Name string `json:"name"` + Path string `json:"path,omitempty"` + Body string `json:"body,omitempty"` + ContentType string `json:"contentType"` +} + +type JavaScriptPlaywrightStdIOEntry struct { + Text *string `json:"text,omitempty"` + Buffer *string `json:"buffer,omitempty"` +} + +type JavaScriptPlaywrightTestStep struct { + Title string `json:"title"` + Duration int `json:"duration"` // milliseconds + Error *JavaScriptPlaywrightTestError `json:"error"` + Steps []JavaScriptPlaywrightTestStep `json:"steps,omitempty"` +} + +type JavaScriptPlaywrightLocation struct { + File string `json:"file"` + Line int `json:"line"` + Column int `json:"column"` +} + +type JavaScriptPlaywrightTestResult struct { + WorkerIndex int `json:"workerIndex"` + // 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted' + Status string `json:"status"` + Duration int `json:"duration"` // milliseconds + Error *JavaScriptPlaywrightTestError `json:"error"` + Errors []JavaScriptPlaywrightReportError `json:"errors"` + Stdout []JavaScriptPlaywrightStdIOEntry `json:"stdout"` + Stderr []JavaScriptPlaywrightStdIOEntry `json:"stderr"` + Retry int `json:"retry"` + Steps []JavaScriptPlaywrightTestStep `json:"steps,omitempty"` + StartTime time.Time `json:"startTime"` + Attachments []JavaScriptPlaywrightAttachment `json:"attachments"` + ErrorLocation *JavaScriptPlaywrightLocation `json:"errorLocation,omitempty"` +} + +type JavaScriptPlaywrightAnnotation struct { + Type string `json:"type"` + Description string `json:"description,omitempty"` +} + +type JavaScriptPlaywrightTest struct { + Timeout int `json:"timeout"` // TODO(kkt) units + Annotations []JavaScriptPlaywrightAnnotation `json:"annotations"` + // 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted' + ExpectedStatus string `json:"expectedStatus"` + ProjectName string `json:"projectName"` + ProjectID string `json:"projectId"` + Results []JavaScriptPlaywrightTestResult `json:"results"` + // 'skipped' | 'expected' | 'unexpected' | 'flaky' + Status string `json:"status"` +} + +type JavaScriptPlaywrightSpec struct { + Tags []string `json:"tags"` + Title string `json:"title"` + Ok bool `json:"ok"` + Tests []JavaScriptPlaywrightTest `json:"tests"` + ID string `json:"id"` + File string `json:"file"` + Line int `json:"line"` + Column int `json:"column"` +} + +type JavaScriptPlaywrightSuite struct { + Title string `json:"title"` + File string `json:"file"` + Column int `json:"column"` + Line int `json:"line"` + Specs []JavaScriptPlaywrightSpec `json:"specs"` + Suites []JavaScriptPlaywrightSuite `json:"suites,omitempty"` +} + +type JavaScriptPlaywrightReport struct { + Config *JavaScriptPlaywrightConfig `json:"config"` + Suites []JavaScriptPlaywrightSuite `json:"suites"` + Errors []JavaScriptPlaywrightTestError `json:"errors"` +} + +type JavaScriptPlaywrightMetaFileAttachment struct { + Name string `json:"name"` + Path string `json:"path,omitempty"` +} + +var javaScriptPlaywrightBacktraceSeparatorRegexp = regexp.MustCompile(`\r?\n\s{4}at`) + +func (p JavaScriptPlaywrightParser) Parse(data io.Reader) (*v1.TestResults, error) { + var report JavaScriptPlaywrightReport + + if err := json.NewDecoder(data).Decode(&report); err != nil { + return nil, errors.NewInputError("Unable to parse test results as JSON: %s", err) + } + if report.Config == nil || report.Suites == nil || report.Errors == nil { + return nil, errors.NewInputError("The JSON does not look like a Playwright report") + } + + otherErrors := make([]v1.OtherError, len(report.Errors)) + for i, err := range report.Errors { + if err.Value != nil { + otherErrors[i] = v1.OtherError{Message: *err.Value} + continue + } + + if err.Message != nil && err.Stack != nil { + stackParts := javaScriptPlaywrightBacktraceSeparatorRegexp.Split(*err.Stack, -1)[1:] + backtrace := make([]string, len(stackParts)) + for i, part := range stackParts { + backtrace[i] = fmt.Sprintf("at%s", part) + } + + otherErrors[i] = v1.OtherError{ + Message: *err.Message, + Backtrace: backtrace, + } + continue + } + + return nil, errors.NewInputError( + "Unexpected error. Errors must have either a value _or_ a message and stack: %v", + err, + ) + } + + tests := make([]v1.Test, 0) + for _, suite := range report.Suites { + foundTests, err := p.testsWithinSuite(suite, []JavaScriptPlaywrightSuite{}) + if err != nil { + return nil, err + } + tests = append(tests, foundTests...) + } + + return v1.NewTestResults( + v1.JavaScriptPlaywrightFramework, + tests, + otherErrors, + ), nil +} + +func (p JavaScriptPlaywrightParser) testsWithinSuite( + suite JavaScriptPlaywrightSuite, + parents []JavaScriptPlaywrightSuite, +) ([]v1.Test, error) { + nestedTests := make([]v1.Test, 0) + nestedParents := make([]JavaScriptPlaywrightSuite, len(parents)+1) + copy(nestedParents, parents) + nestedParents[len(parents)] = suite + + for _, nestedSuite := range suite.Suites { + mappedTests, err := p.testsWithinSuite(nestedSuite, nestedParents) + if err != nil { + return nil, err + } + nestedTests = append(nestedTests, mappedTests...) + } + + tests := make([]v1.Test, 0) + for _, spec := range suite.Specs { + for _, test := range spec.Tests { + lineage := make([]string, 0) + for _, parent := range nestedParents { + // We differentiate by file already in v1.Test.Location.File + if parent.File == parent.Title { + continue + } + + lineage = append(lineage, parent.Title) + } + lineage = append(lineage, spec.Title) + + line := spec.Line + column := spec.Column + location := v1.Location{ + File: spec.File, + Line: &line, + Column: &column, + } + + project := test.ProjectName + attempt := v1.TestAttempt{ + Status: v1.NewSkippedTestStatus(nil), + Meta: map[string]any{ + "annotations": test.Annotations, + "project": project, + "tags": spec.Tags, + }, + } + pastAttempts := make([]v1.TestAttempt, 0) + resultCount := len(test.Results) + for i, result := range test.Results { + duration := time.Duration(result.Duration * int(time.Millisecond)) + startedAt := result.StartTime + + stderrLines := make([]string, len(result.Stderr)) + for i, entry := range result.Stderr { + if entry.Buffer != nil { + stderrLines[i] = *entry.Buffer + } + if entry.Text != nil { + stderrLines[i] = *entry.Text + } + } + stderr := strings.Join(stderrLines, "") + + stdoutLines := make([]string, len(result.Stdout)) + for i, entry := range result.Stdout { + if entry.Buffer != nil { + stdoutLines[i] = *entry.Buffer + } + if entry.Text != nil { + stdoutLines[i] = *entry.Text + } + } + stdout := strings.Join(stdoutLines, "") + + var status v1.TestStatus + switch result.Status { + case "passed": + status = v1.NewSuccessfulTestStatus() + case "failed": + var message *string + var backtrace []string + + if result.Error != nil { + message = result.Error.Message + + if result.Error.Stack != nil { + stackParts := javaScriptPlaywrightBacktraceSeparatorRegexp.Split(*result.Error.Stack, -1)[1:] + for _, part := range stackParts { + backtrace = append(backtrace, fmt.Sprintf("at%s", part)) + } + } + } + + status = v1.NewFailedTestStatus(message, nil, backtrace) + case "timedOut": + var message *string + var backtrace []string + + if result.Error != nil { + message = result.Error.Message + + if result.Error.Stack != nil { + stackParts := javaScriptPlaywrightBacktraceSeparatorRegexp.Split(*result.Error.Stack, -1)[1:] + for _, part := range stackParts { + backtrace = append(backtrace, fmt.Sprintf("at%s", part)) + } + } + } + + status = v1.NewTimedOutTestStatus(message, nil, backtrace) + case "skipped": + status = v1.NewSkippedTestStatus(nil) + case "interrupted": + status = v1.NewCanceledTestStatus() + default: + return nil, errors.NewInputError("Unexpected test results status: %v", result.Status) + } + + meta := map[string]any{ + "annotations": test.Annotations, + "project": test.ProjectName, + "tags": spec.Tags, + } + + if len(result.Attachments) > 0 { + attachments := make([]JavaScriptPlaywrightMetaFileAttachment, len(result.Attachments)) + + for j, attachment := range result.Attachments { + attachments[j] = JavaScriptPlaywrightMetaFileAttachment{ + Name: attachment.Name, + Path: attachment.Path, + } + } + + meta["fileAttachments"] = attachments + } + + workingAttempt := v1.TestAttempt{ + Duration: &duration, + Meta: meta, + Status: status, + Stderr: &stderr, + Stdout: &stdout, + StartedAt: &startedAt, + } + + if i == resultCount-1 { + attempt = workingAttempt + } else { + pastAttempts = append(pastAttempts, workingAttempt) + } + } + + if test.ExpectedStatus == "failed" { + if attempt.Status.Kind == v1.TestStatusFailed { + attempt.Status = v1.NewSuccessfulTestStatus() + } else { + message := "Expected the test to fail, but it did not" + attempt.Status = v1.NewFailedTestStatus(&message, nil, nil) + } + } + + tests = append(tests, v1.Test{ + Scope: &project, + Name: strings.Join(lineage, " "), + Lineage: lineage, + Location: &location, + Attempt: attempt, + PastAttempts: pastAttempts, + }) + } + } + + tests = append(tests, nestedTests...) + return tests, nil +} diff --git a/internal/captain/parsing/javascript_playwright_parser_test.go b/internal/captain/parsing/javascript_playwright_parser_test.go new file mode 100644 index 0000000..a20a3b4 --- /dev/null +++ b/internal/captain/parsing/javascript_playwright_parser_test.go @@ -0,0 +1,107 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptPlaywrightParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/playwright.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptPlaywrightParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("sets the scope to the project", func() { + fixture, err := os.Open("../../test/fixtures/playwright.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptPlaywrightParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + test := testResults.Tests[0] + Expect(*test.Scope).To(SatisfyAny(Equal("chromium"), Equal("firefox"))) + }) + + It("parses the sample file with other errors", func() { + fixture, err := os.Open("../../test/fixtures/playwright_with_other_errors.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptPlaywrightParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses the sample file with an error in global setup", func() { + fixture, err := os.Open("../../test/fixtures/playwright_global_other_error.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptPlaywrightParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("errors on malformed JSON", func() { + testResults, err := parsing.JavaScriptPlaywrightParser{}.Parse(strings.NewReader(`{`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as JSON")) + Expect(testResults).To(BeNil()) + }) + + It("errors on JSON that doesn't look like Playwright", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.JavaScriptPlaywrightParser{}.Parse(strings.NewReader(`{}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("The JSON does not look like a Playwright report")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptPlaywrightParser{}.Parse(strings.NewReader( + `{"suites":[],"errors":[]}`, + )) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("The JSON does not look like a Playwright report")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptPlaywrightParser{}.Parse(strings.NewReader( + `{"config":{},"errors":[]}`, + )) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("The JSON does not look like a Playwright report")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptPlaywrightParser{}.Parse(strings.NewReader( + `{"config":{},"suites":[]}`, + )) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("The JSON does not look like a Playwright report")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptPlaywrightParser{}.Parse(strings.NewReader( + `{"config":{},"suites":[],"errors":[]}`, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/javascript_vitest_parser.go b/internal/captain/parsing/javascript_vitest_parser.go new file mode 100644 index 0000000..f561eb1 --- /dev/null +++ b/internal/captain/parsing/javascript_vitest_parser.go @@ -0,0 +1,198 @@ +package parsing + +import ( + "encoding/json" + "fmt" + "io" + "regexp" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JavaScriptVitestParser struct{} + +// https://github.com/vitest-dev/vitest/blob/95f0203f27f5659f5758638edc4d1d90283801ac/packages/snapshot/src/types/index.ts#L47-L50 +type JavaScriptVitestUncheckedSnapshot struct { + FilePath string `json:"filePath"` + Keys []string `json:"keys"` +} + +// https://github.com/vitest-dev/vitest/blob/95f0203f27f5659f5758638edc4d1d90283801ac/packages/snapshot/src/types/index.ts#L52-L67 +type JavaScriptVitestSnapshot struct { + Added int `json:"added"` + DidUpdate bool `json:"didUpdate"` + Failure bool `json:"failure"` + FilesAdded int `json:"filesAdded"` + FilesRemoved int `json:"filesRemoved"` + FilesRemovedList []string `json:"filesRemovedList"` + FilesUnmatched int `json:"filesUnmatched"` + FilesUpdated int `json:"filesUpdated"` + Matched int `json:"matched"` + Total int `json:"total"` + Unchecked int `json:"unchecked"` + UncheckedKeysByFile []JavaScriptVitestUncheckedSnapshot `json:"uncheckedKeysByFile"` + Unmatched int `json:"unmatched"` + Updated int `json:"updated"` +} + +// https://github.com/vitest-dev/vitest/blob/95f0203f27f5659f5758638edc4d1d90283801ac/packages/vitest/src/node/reporters/json.ts#L16-L19 +type JavaScriptVitestCallsite struct { + Column int `json:"column"` + Line int `json:"line"` +} + +// https://github.com/vitest-dev/vitest/blob/95f0203f27f5659f5758638edc4d1d90283801ac/packages/runner/src/types/tasks.ts#L105 +type JavaScriptVitestTaskMeta struct{} + +// https://github.com/vitest-dev/vitest/blob/95f0203f27f5659f5758638edc4d1d90283801ac/packages/vitest/src/node/reporters/json.ts#L30-L39 +type JavaScriptVitestAssertionResult struct { + AncestorTitles []string `json:"ancestorTitles"` + Duration *float64 `json:"duration"` + FailureMessages []string `json:"failureMessages"` + FullName string `json:"fullName"` + Location *JavaScriptVitestCallsite `json:"location"` + Status string `json:"status"` + Title string `json:"title"` + Meta JavaScriptVitestTaskMeta `json:"meta"` +} + +// https://github.com/vitest-dev/vitest/blob/95f0203f27f5659f5758638edc4d1d90283801ac/packages/vitest/src/node/reporters/json.ts#L41-L50 +type JavaScriptVitestTestResult struct { + AssertionResults []JavaScriptVitestAssertionResult `json:"assertionResults"` + EndTime float64 `json:"endTime"` + Message string `json:"message"` + Name string `json:"name"` + StartTime float64 `json:"startTime"` + Status string `json:"status"` +} + +// https://github.com/vitest-dev/vitest/blob/95f0203f27f5659f5758638edc4d1d90283801ac/packages/vitest/src/node/reporters/json.ts#L52-L69 +type JavaScriptVitestTestResults struct { + NumFailedTests int `json:"numFailedTests"` + NumFailedTestSuites int `json:"numFailedTestSuites"` + NumPassedTests int `json:"numPassedTests"` + NumPassedTestSuites int `json:"numPassedTestSuites"` + NumPendingTests int `json:"numPendingTests"` + NumPendingTestSuites int `json:"numPendingTestSuites"` + NumTodoTests int `json:"numTodoTests"` + NumTotalTests int `json:"numTotalTests"` + NumTotalTestSuites int `json:"numTotalTestSuites"` + Snapshot *JavaScriptVitestSnapshot `json:"snapshot"` + StartTime float64 `json:"startTime"` + Success bool `json:"success"` + TestResults []JavaScriptVitestTestResult `json:"testResults"` +} + +var javaScriptVitestBacktraceSeparatorRegexp = regexp.MustCompile(`\r?\n\s{4}at`) + +func (p JavaScriptVitestParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testResults JavaScriptVitestTestResults + + if err := json.NewDecoder(data).Decode(&testResults); err != nil { + return nil, errors.NewInputError("Unable to parse test results as JSON: %s", err) + } + if testResults.TestResults == nil { + return nil, errors.NewInputError("No test results were found in the JSON") + } + if testResults.Snapshot == nil { + return nil, errors.NewInputError("No snapshot was found in the JSON") + } + + tests := make([]v1.Test, 0) + otherErrors := make([]v1.OtherError, 0) + for _, testResult := range testResults.TestResults { + sawFailedTest := false + file := testResult.Name + + for _, assertionResult := range testResult.AssertionResults { + lineage := assertionResult.AncestorTitles + lineage = append(lineage, assertionResult.Title) + name := strings.Join(lineage, " > ") + + var line *int + var column *int + if assertionResult.Location != nil { + assertionResult := assertionResult + line = &assertionResult.Location.Line + column = &assertionResult.Location.Column + } + location := v1.Location{File: file, Line: line, Column: column} + + var duration *time.Duration + if assertionResult.Duration != nil { + transformedDuration := time.Duration(*assertionResult.Duration * float64(time.Millisecond)) + duration = &transformedDuration + } + + var status v1.TestStatus + switch assertionResult.Status { + case "passed": + status = v1.NewSuccessfulTestStatus() + case "failed": + message, backtrace := p.extractFailureMetadata(assertionResult.FailureMessages) + status = v1.NewFailedTestStatus(message, nil, backtrace) + sawFailedTest = true + case "skipped": + status = v1.NewSkippedTestStatus(nil) + case "pending": + status = v1.NewPendedTestStatus(nil) + case "todo": + status = v1.NewTodoTestStatus(nil) + default: + return nil, errors.NewInputError( + "Unexpected status %q for assertion result %v", + assertionResult.Status, + assertionResult, + ) + } + + attempt := v1.TestAttempt{Duration: duration, Status: status} + tests = append( + tests, + v1.Test{ + Name: name, + Lineage: lineage, + Location: &location, + Attempt: attempt, + }, + ) + } + + if !sawFailedTest && testResult.Status == "failed" { + if len(testResult.Name) > 0 { + otherErrors = append(otherErrors, v1.OtherError{ + Message: testResult.Message, + Location: &v1.Location{File: testResult.Name}, + }) + } else { + otherErrors = append(otherErrors, v1.OtherError{Message: testResult.Message}) + } + } + } + + return v1.NewTestResults( + v1.JavaScriptVitestFramework, + tests, + otherErrors, + ), nil +} + +func (p JavaScriptVitestParser) extractFailureMetadata(failureMessages []string) (*string, []string) { + var message *string + var backtrace []string + + if failureMessages != nil && failureMessages[0] != "" { + parts := javaScriptVitestBacktraceSeparatorRegexp.Split(failureMessages[0], -1) + first, rest := parts[0], parts[1:] + message = &first + + for _, part := range rest { + backtrace = append(backtrace, fmt.Sprintf("at%s", part)) + } + } + + return message, backtrace +} diff --git a/internal/captain/parsing/javascript_vitest_parser_test.go b/internal/captain/parsing/javascript_vitest_parser_test.go new file mode 100644 index 0000000..5bda300 --- /dev/null +++ b/internal/captain/parsing/javascript_vitest_parser_test.go @@ -0,0 +1,353 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + "time" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptVitestParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/vitest.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptVitestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses empty files", func() { + testResults, err := parsing.JavaScriptVitestParser{}.Parse(strings.NewReader(`{"testResults": [], "snapshot": {}}`)) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults.Tests).To(HaveLen(0)) + }) + + It("errors on malformed JSON", func() { + testResults, err := parsing.JavaScriptVitestParser{}.Parse(strings.NewReader(`{`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as JSON")) + Expect(testResults).To(BeNil()) + }) + + It("errors on JSON that doesn't look like Vitest", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.JavaScriptVitestParser{}.Parse(strings.NewReader(`{}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No test results were found in the JSON")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JavaScriptVitestParser{}.Parse(strings.NewReader(`{"testResults": []}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No snapshot was found in the JSON")) + Expect(testResults).To(BeNil()) + }) + + It("parses a minimally failing test", func() { + durationInMilliseconds := 653.0 + jestResults := parsing.JavaScriptVitestTestResults{ + Snapshot: &parsing.JavaScriptVitestSnapshot{}, + TestResults: []parsing.JavaScriptVitestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptVitestAssertionResult{ + { + Duration: &durationInMilliseconds, + Status: "failed", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptVitestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + expectedDuration := time.Duration(653000000) + Expect(testResults.Tests[0]).To(Equal( + v1.Test{ + Name: "title", + Lineage: []string{"title"}, + Location: &v1.Location{File: "/some/path/to/name/of/file.js"}, + Attempt: v1.TestAttempt{ + Duration: &expectedDuration, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + )) + }) + + It("parses a maximally failing test", func() { + durationInMilliseconds := 653.0 + jestResults := parsing.JavaScriptVitestTestResults{ + Snapshot: &parsing.JavaScriptVitestSnapshot{}, + TestResults: []parsing.JavaScriptVitestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptVitestAssertionResult{ + { + AncestorTitles: []string{"part 1", "part 2"}, + Duration: &durationInMilliseconds, + FailureMessages: []string{"final\n\nmessage\n at stacktrace"}, + Status: "failed", + Title: "title", + Location: &parsing.JavaScriptVitestCallsite{Line: 10, Column: 12}, + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptVitestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + expectedDuration := time.Duration(653000000) + line := 10 + column := 12 + finalFailureMessage := "final\n\nmessage" + finalBacktrace := []string{"at stacktrace"} + Expect(testResults.Tests[0]).To(Equal( + v1.Test{ + Name: "part 1 > part 2 > title", + Lineage: []string{"part 1", "part 2", "title"}, + Location: &v1.Location{File: "/some/path/to/name/of/file.js", Line: &line, Column: &column}, + Attempt: v1.TestAttempt{ + Duration: &expectedDuration, + Status: v1.NewFailedTestStatus(&finalFailureMessage, nil, finalBacktrace), + }, + }, + )) + }) + + It("parses passed statuses", func() { + durationInMilliseconds := 653.0 + jestResults := parsing.JavaScriptVitestTestResults{ + Snapshot: &parsing.JavaScriptVitestSnapshot{}, + TestResults: []parsing.JavaScriptVitestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptVitestAssertionResult{ + { + Duration: &durationInMilliseconds, + Status: "passed", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptVitestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Attempt.Status).To(Equal(v1.NewSuccessfulTestStatus())) + }) + + It("parses todo statuses", func() { + jestResults := parsing.JavaScriptVitestTestResults{ + Snapshot: &parsing.JavaScriptVitestSnapshot{}, + TestResults: []parsing.JavaScriptVitestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptVitestAssertionResult{ + { + Status: "todo", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptVitestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Attempt.Duration).To(BeNil()) + Expect(testResults.Tests[0].Attempt.Status).To(Equal(v1.NewTodoTestStatus(nil))) + }) + + It("parses skipped statuses", func() { + jestResults := parsing.JavaScriptVitestTestResults{ + Snapshot: &parsing.JavaScriptVitestSnapshot{}, + TestResults: []parsing.JavaScriptVitestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptVitestAssertionResult{ + { + Status: "skipped", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptVitestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Attempt.Status).To(Equal(v1.NewSkippedTestStatus(nil))) + }) + + It("parses pending statuses", func() { + jestResults := parsing.JavaScriptVitestTestResults{ + Snapshot: &parsing.JavaScriptVitestSnapshot{}, + TestResults: []parsing.JavaScriptVitestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptVitestAssertionResult{ + { + Status: "pending", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptVitestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Attempt.Status).To(Equal(v1.NewPendedTestStatus(nil))) + }) + + It("errors on other statuses", func() { + jestResults := parsing.JavaScriptVitestTestResults{ + Snapshot: &parsing.JavaScriptVitestSnapshot{}, + TestResults: []parsing.JavaScriptVitestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "passed", + AssertionResults: []parsing.JavaScriptVitestAssertionResult{ + { + Status: "wat", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptVitestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unexpected status \"wat\"")) + Expect(testResults).To(BeNil()) + }) + + It("parses an other error when a whole test result fails", func() { + jestResults := parsing.JavaScriptVitestTestResults{ + Snapshot: &parsing.JavaScriptVitestSnapshot{}, + TestResults: []parsing.JavaScriptVitestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "failed", + Message: "the reason it failed", + AssertionResults: []parsing.JavaScriptVitestAssertionResult{ + { + Status: "passed", + Title: "title", + }, + }, + }, + { + Name: "", + Status: "failed", + Message: "no name", + AssertionResults: []parsing.JavaScriptVitestAssertionResult{ + { + Status: "passed", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptVitestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.OtherErrors[0]).To(Equal( + v1.OtherError{ + Location: &v1.Location{File: "/some/path/to/name/of/file.js"}, + Message: "the reason it failed", + }, + )) + Expect(testResults.OtherErrors[1]).To(Equal(v1.OtherError{Message: "no name"})) + Expect(testResults.Tests[0]).NotTo(BeNil()) + }) + + It("does not parse an other error when a whole test result fails due to a failing test", func() { + jestResults := parsing.JavaScriptVitestTestResults{ + Snapshot: &parsing.JavaScriptVitestSnapshot{}, + TestResults: []parsing.JavaScriptVitestTestResult{ + { + Name: "/some/path/to/name/of/file.js", + Status: "failed", + Message: "the reason it failed", + AssertionResults: []parsing.JavaScriptVitestAssertionResult{ + { + Status: "failed", + Title: "title", + }, + }, + }, + }, + } + data, err := json.Marshal(jestResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JavaScriptVitestParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.OtherErrors).To(HaveLen(0)) + Expect(testResults.Tests[0]).NotTo(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/junit_testsuite_parser.go b/internal/captain/parsing/junit_testsuite_parser.go new file mode 100644 index 0000000..9a2dd37 --- /dev/null +++ b/internal/captain/parsing/junit_testsuite_parser.go @@ -0,0 +1,134 @@ +package parsing + +import ( + "encoding/xml" + "fmt" + "io" + "math" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JUnitTestsuiteParser struct{} + +func (p JUnitTestsuiteParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testSuite JUnitTestSuite + + if err := xml.NewDecoder(data).Decode(&testSuite); err != nil { + return nil, errors.NewInputError("Unable to parse test results as XML: %s", err) + } + + if testSuite.Tests == nil { + return nil, errors.NewInputError("The test suite in the XML does not appear to match JUnit XML") + } + + tests := make([]v1.Test, 0) + var properties map[string]any + if len(testSuite.Properties) > 0 { + properties = make(map[string]any) + } + for _, property := range testSuite.Properties { + properties[property.Name] = property.Value + } + + for _, testCase := range testSuite.TestCases { + // The a lot of reporter libraries allow switching these + // We want the one that has the entire description (contains the short description) + // e.g. classname="Some Tests with some context it passes" name="it passes" + // We'd want the classname in the above case. Classname contains name, but name doesn't contain classname + var name string + switch { + case strings.Contains(testCase.Name, testCase.ClassName) && strings.Contains(testCase.ClassName, testCase.Name): + name = testCase.Name + case !strings.Contains(testCase.Name, testCase.ClassName) && strings.Contains(testCase.ClassName, testCase.Name): + name = testCase.ClassName + case strings.Contains(testCase.Name, testCase.ClassName) && !strings.Contains(testCase.ClassName, testCase.Name): + name = testCase.Name + case !strings.Contains(testCase.Name, testCase.ClassName) && !strings.Contains(testCase.ClassName, testCase.Name): + name = fmt.Sprintf("%s %s", testCase.ClassName, testCase.Name) + default: + return nil, errors.NewInternalError("Unreachable: reached default case of exhaustive switch statement") + } + + duration := time.Duration(math.Round(testCase.Time * float64(time.Second))) + + var status v1.TestStatus + switch { + case testCase.Failure != nil: + status = p.NewFailedTestStatus(*testCase.Failure) + case testCase.Error != nil: + status = p.NewFailedTestStatus(*testCase.Error) + case testCase.Skipped != nil: + status = v1.NewSkippedTestStatus(testCase.Skipped.Message) + default: + status = v1.NewSuccessfulTestStatus() + } + + var location *v1.Location + switch { + case testCase.File != nil: + location = &v1.Location{File: *testCase.File} + case testSuite.File != nil: + location = &v1.Location{File: *testSuite.File} + default: + location = nil + } + switch { + case location != nil && testCase.Line != nil: + location.Line = testCase.Line + case location != nil && testCase.Lineno != nil: + location.Line = testCase.Lineno + default: + // nothing to do here + } + + tests = append( + tests, + v1.Test{ + Name: name, + Location: location, + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: properties, + Status: status, + Stderr: testCase.SystemErr, + Stdout: testCase.SystemOut, + }, + }, + ) + } + + return v1.NewTestResults( + v1.NewOtherFramework(nil, nil), + tests, + nil, + ), nil +} + +func (p JUnitTestsuiteParser) NewFailedTestStatus(failure JUnitFailure) v1.TestStatus { + failureMessage := failure.Message + failureException := failure.Type + + // The JUnit spec suggests this be used for a stack trace, so we'll assume such but it could be wrong. + var lines []string + switch { + case failure.CDataContents != nil: + lines = jUnitNewlineRegexp.Split(*failure.CDataContents, -1) + case failure.ChardataContents != nil: + lines = jUnitNewlineRegexp.Split(*failure.ChardataContents, -1) + default: + lines = nil + } + if len(lines) == 0 { + return v1.NewFailedTestStatus(failureMessage, failureException, nil) + } + + backtrace := make([]string, 0) + for _, line := range lines { + backtrace = append(backtrace, strings.TrimSpace(line)) + } + return v1.NewFailedTestStatus(failureMessage, failureException, backtrace) +} diff --git a/internal/captain/parsing/junit_testsuite_parser_test.go b/internal/captain/parsing/junit_testsuite_parser_test.go new file mode 100644 index 0000000..d616cd4 --- /dev/null +++ b/internal/captain/parsing/junit_testsuite_parser_test.go @@ -0,0 +1,316 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + "time" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JUnitTestsuiteParser", func() { + Describe("Parse", func() { + It("parses the sample testsuite file", func() { + fixture, err := os.Open("../../test/fixtures/junit-no-testsuites-element.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("does not parse the sample testsuites file", func() { + fixture, err := os.Open("../../test/fixtures/junit.xml") + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(fixture) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as XML")) + Expect(testResults).To(BeNil()) + }) + + It("errors on malformed XML", func() { + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(strings.NewReader(``)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as XML")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.JUnitTestsuiteParser{}.Parse( + strings.NewReader(``), + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring("The test suite in the XML does not appear to match JUnit XML"), + ) + Expect(testResults).To(BeNil()) + }) + + It("extracts the file from a test case", func() { + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(strings.NewReader( + ` + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + line := 12 + Expect(*testResults.Tests[0].Location).To(Equal( + v1.Location{File: "some/path/to/file.js", Line: &line}, + )) + }) + + It("parses the duration as seconds", func() { + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(strings.NewReader( + ` + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(*testResults.Tests[0].Attempt.Duration).To(Equal( + time.Duration(1524900000), + )) + }) + + It("parses failures with inner CDATA", func() { + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(strings.NewReader( + ` + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + message := "some message" + exception := "someclass" + backtrace := []string{ + "line 1", + "line 2", + "", + "line 3", + } + Expect(testResults.Tests[0].Attempt.Status).To(Equal( + v1.NewFailedTestStatus(&message, &exception, backtrace), + )) + }) + + It("parses failures with inner chardata", func() { + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(strings.NewReader( + ` + + + line 1 + line 2 + + line 3 + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + message := "some message" + exception := "someclass" + backtrace := []string{ + "line 1", + "line 2", + "", + "line 3", + } + Expect(testResults.Tests[0].Attempt.Status).To(Equal( + v1.NewFailedTestStatus(&message, &exception, backtrace), + )) + }) + + It("parses errors with inner CDATA", func() { + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(strings.NewReader( + ` + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + message := "some message" + exception := "someclass" + backtrace := []string{ + "line 1", + "line 2", + "", + "line 3", + } + Expect(testResults.Tests[0].Attempt.Status).To(Equal( + v1.NewFailedTestStatus(&message, &exception, backtrace), + )) + }) + + It("parses errors with inner chardata", func() { + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(strings.NewReader( + ` + + + line 1 + line 2 + + line 3 + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + message := "some message" + exception := "someclass" + backtrace := []string{ + "line 1", + "line 2", + "", + "line 3", + } + Expect(testResults.Tests[0].Attempt.Status).To(Equal( + v1.NewFailedTestStatus(&message, &exception, backtrace), + )) + }) + + It("parses skipped tests with messages", func() { + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(strings.NewReader( + ` + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + message := "some reason" + Expect(testResults.Tests[0].Attempt.Status).To(Equal( + v1.NewSkippedTestStatus(&message), + )) + }) + + It("calculates the correct name when the classname contains the name", func() { + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(strings.NewReader( + ` + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Name).To(Equal("prefix some test name")) + }) + + It("calculates the correct name when the name contains the classname", func() { + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(strings.NewReader( + ` + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Name).To(Equal("prefix some test name")) + }) + + It("calculates the correct name when the name is the same as classname", func() { + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(strings.NewReader( + ` + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Name).To(Equal("prefix some test name")) + }) + + It("calculates the correct name when the name is entirely different from the classname", func() { + testResults, err := parsing.JUnitTestsuiteParser{}.Parse(strings.NewReader( + ` + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Name).To(Equal("prefix some test name")) + }) + }) +}) diff --git a/internal/captain/parsing/junit_testsuites_parser.go b/internal/captain/parsing/junit_testsuites_parser.go new file mode 100644 index 0000000..1201ecc --- /dev/null +++ b/internal/captain/parsing/junit_testsuites_parser.go @@ -0,0 +1,202 @@ +package parsing + +import ( + "encoding/xml" + "fmt" + "io" + "math" + "regexp" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JUnitTestsuitesParser struct{} + +type JUnitFailure struct { + Type *string `xml:"type,attr"` + Message *string `xml:"message,attr"` + CDataContents *string `xml:",cdata"` + ChardataContents *string `xml:",chardata"` +} + +type JUnitSkipped struct { + Message *string `xml:"message,attr"` +} + +type JUnitTestCase struct { + ClassName string `xml:"classname,attr,omitempty"` + Error *JUnitFailure `xml:"error"` + Failure *JUnitFailure `xml:"failure"` + Name string `xml:"name,attr"` + Skipped *JUnitSkipped `xml:"skipped"` + SystemErr *string `xml:"system-err"` + SystemOut *string `xml:"system-out"` + Time float64 `xml:"time,attr"` + + // out of spec, but maybe interesting + File *string `xml:"file,attr"` + Line *int `xml:"line,attr"` + Lineno *int `xml:"lineno,attr"` + + XMLName xml.Name `xml:"testcase"` +} + +type JUnitProperty struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} + +type JUnitTestSuite struct { + Errors int `xml:"errors,attr"` + Failures int `xml:"failures,attr"` + Name string `xml:"name,attr"` + Skipped int `xml:"skipped,attr"` + TestCases []JUnitTestCase `xml:"testcase"` + TestSuites []JUnitTestSuite `xml:"testsuite"` + Properties []JUnitProperty `xml:"properties>property"` + Tests *int `xml:"tests,attr"` + Time float64 `xml:"time,attr,omitempty"` + Timestamp string `xml:"timestamp,attr,omitempty"` + + // out of spec, but maybe interesting + File *string `xml:"file,attr"` + + XMLName xml.Name `xml:"testsuite"` +} + +type JUnitTestResults struct { + TestSuites []JUnitTestSuite `xml:"testsuite"` + XMLName xml.Name `xml:"testsuites"` +} + +var jUnitNewlineRegexp = regexp.MustCompile(`\r?\n`) + +// collectTestCases recursively collects all testcases from a testsuite and its nested testsuites +func collectTestCases(suite JUnitTestSuite) []JUnitTestCase { + testCases := make([]JUnitTestCase, 0, len(suite.TestCases)) + testCases = append(testCases, suite.TestCases...) + for _, nestedSuite := range suite.TestSuites { + testCases = append(testCases, collectTestCases(nestedSuite)...) + } + return testCases +} + +func (p JUnitTestsuitesParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testResults JUnitTestResults + + if err := xml.NewDecoder(data).Decode(&testResults); err != nil { + return nil, errors.NewInputError("Unable to parse test results as XML: %s", err) + } + + tests := make([]v1.Test, 0) + for _, testSuite := range testResults.TestSuites { + var properties map[string]any + if len(testSuite.Properties) > 0 { + properties = make(map[string]any) + } + for _, property := range testSuite.Properties { + properties[property.Name] = property.Value + } + + for _, testCase := range collectTestCases(testSuite) { + // The a lot of reporter libraries allow switching these + // We want the one that has the entire description (contains the short description) + // e.g. classname="Some Tests with some context it passes" name="it passes" + // We'd want the classname in the above case. Classname contains name, but name doesn't contain classname + var name string + switch { + case strings.Contains(testCase.Name, testCase.ClassName) && strings.Contains(testCase.ClassName, testCase.Name): + name = testCase.Name + case !strings.Contains(testCase.Name, testCase.ClassName) && strings.Contains(testCase.ClassName, testCase.Name): + name = testCase.ClassName + case strings.Contains(testCase.Name, testCase.ClassName) && !strings.Contains(testCase.ClassName, testCase.Name): + name = testCase.Name + case !strings.Contains(testCase.Name, testCase.ClassName) && !strings.Contains(testCase.ClassName, testCase.Name): + name = fmt.Sprintf("%s %s", testCase.ClassName, testCase.Name) + default: + return nil, errors.NewInternalError("Unreachable: reached default case of exhaustive switch statement") + } + + duration := time.Duration(math.Round(testCase.Time * float64(time.Second))) + + var status v1.TestStatus + switch { + case testCase.Failure != nil: + status = p.NewFailedTestStatus(*testCase.Failure) + case testCase.Error != nil: + status = p.NewFailedTestStatus(*testCase.Error) + case testCase.Skipped != nil: + status = v1.NewSkippedTestStatus(testCase.Skipped.Message) + default: + status = v1.NewSuccessfulTestStatus() + } + + var location *v1.Location + switch { + case testCase.File != nil: + location = &v1.Location{File: *testCase.File} + case testSuite.File != nil: + location = &v1.Location{File: *testSuite.File} + default: + location = nil + } + switch { + case location != nil && testCase.Line != nil: + location.Line = testCase.Line + case location != nil && testCase.Lineno != nil: + location.Line = testCase.Lineno + default: + // nothing to do here + } + + tests = append( + tests, + v1.Test{ + Name: name, + Location: location, + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: properties, + Status: status, + Stderr: testCase.SystemErr, + Stdout: testCase.SystemOut, + }, + }, + ) + } + } + + return v1.NewTestResults( + v1.NewOtherFramework(nil, nil), + tests, + nil, + ), nil +} + +func (p JUnitTestsuitesParser) NewFailedTestStatus(failure JUnitFailure) v1.TestStatus { + failureMessage := failure.Message + failureException := failure.Type + + // The JUnit spec suggests this be used for a stack trace, so we'll assume such but it could be wrong. + var lines []string + switch { + case failure.CDataContents != nil: + lines = jUnitNewlineRegexp.Split(*failure.CDataContents, -1) + case failure.ChardataContents != nil: + lines = jUnitNewlineRegexp.Split(*failure.ChardataContents, -1) + default: + lines = nil + } + if len(lines) == 0 { + return v1.NewFailedTestStatus(failureMessage, failureException, nil) + } + + backtrace := make([]string, 0) + for _, line := range lines { + backtrace = append(backtrace, strings.TrimSpace(line)) + } + return v1.NewFailedTestStatus(failureMessage, failureException, backtrace) +} diff --git a/internal/captain/parsing/junit_testsuites_parser_test.go b/internal/captain/parsing/junit_testsuites_parser_test.go new file mode 100644 index 0000000..db77ced --- /dev/null +++ b/internal/captain/parsing/junit_testsuites_parser_test.go @@ -0,0 +1,361 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + "time" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JUnitTestsuitesParser", func() { + Describe("Parse", func() { + It("parses the sample testsuites file", func() { + fixture, err := os.Open("../../test/fixtures/junit.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("does not parse the sample testsuite file", func() { + fixture, err := os.Open("../../test/fixtures/junit-no-testsuites-element.xml") + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(fixture) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as XML")) + Expect(testResults).To(BeNil()) + }) + + It("parses empty files", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader(``)) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults.Tests).To(HaveLen(0)) + }) + + It("errors on malformed XML", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader(``)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as XML")) + Expect(testResults).To(BeNil()) + }) + + It("extracts the file from a test case", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader( + ` + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + line := 12 + Expect(*testResults.Tests[0].Location).To(Equal( + v1.Location{File: "some/path/to/file.js", Line: &line}, + )) + }) + + It("parses the duration as seconds", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader( + ` + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(*testResults.Tests[0].Attempt.Duration).To(Equal( + time.Duration(1524900000), + )) + }) + + It("parses failures with inner CDATA", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader( + ` + + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + message := "some message" + exception := "someclass" + backtrace := []string{ + "line 1", + "line 2", + "", + "line 3", + } + Expect(testResults.Tests[0].Attempt.Status).To(Equal( + v1.NewFailedTestStatus(&message, &exception, backtrace), + )) + }) + + It("parses failures with inner chardata", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader( + ` + + + + line 1 + line 2 + + line 3 + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + message := "some message" + exception := "someclass" + backtrace := []string{ + "line 1", + "line 2", + "", + "line 3", + } + Expect(testResults.Tests[0].Attempt.Status).To(Equal( + v1.NewFailedTestStatus(&message, &exception, backtrace), + )) + }) + + It("parses errors with inner CDATA", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader( + ` + + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + message := "some message" + exception := "someclass" + backtrace := []string{ + "line 1", + "line 2", + "", + "line 3", + } + Expect(testResults.Tests[0].Attempt.Status).To(Equal( + v1.NewFailedTestStatus(&message, &exception, backtrace), + )) + }) + + It("parses errors with inner chardata", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader( + ` + + + + line 1 + line 2 + + line 3 + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + message := "some message" + exception := "someclass" + backtrace := []string{ + "line 1", + "line 2", + "", + "line 3", + } + Expect(testResults.Tests[0].Attempt.Status).To(Equal( + v1.NewFailedTestStatus(&message, &exception, backtrace), + )) + }) + + It("parses skipped tests with messages", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader( + ` + + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + message := "some reason" + Expect(testResults.Tests[0].Attempt.Status).To(Equal( + v1.NewSkippedTestStatus(&message), + )) + }) + + It("calculates the correct name when the classname contains the name", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader( + ` + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Name).To(Equal("prefix some test name")) + }) + + It("calculates the correct name when the name contains the classname", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader( + ` + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Name).To(Equal("prefix some test name")) + }) + + It("calculates the correct name when the name is the same as classname", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader( + ` + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Name).To(Equal("prefix some test name")) + }) + + It("calculates the correct name when the name is entirely different from the classname", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader( + ` + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests[0].Name).To(Equal("prefix some test name")) + }) + + It("parses nested testsuites (bun test format)", func() { + testResults, err := parsing.JUnitTestsuitesParser{}.Parse(strings.NewReader( + ` + + + + + + + + + + + + + + `, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + Expect(testResults.Tests).To(HaveLen(3)) + Expect(testResults.Tests[0].Name).To(Equal("#method > Suite works")) + Expect(testResults.Tests[1].Name).To(Equal("nested > #method > Suite test 1")) + Expect(testResults.Tests[2].Name).To(Equal("nested > #method > Suite test 2")) + }) + }) +}) diff --git a/internal/captain/parsing/parse.go b/internal/captain/parsing/parse.go new file mode 100644 index 0000000..9c92adb --- /dev/null +++ b/internal/captain/parsing/parse.go @@ -0,0 +1,239 @@ +// parsing holds the functionality to attempt to parse a test results file and the parsers themselves +// the parsers will produce a testingschema test result or an error +// in the case where no parsers are capable of parsing test results, an error will be returned indicating so +package parsing + +import ( + "encoding/base64" + "fmt" + "io" + "strings" + + "go.uber.org/zap" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/fs" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type Config struct { + ProvidedFrameworkKind string + ProvidedFrameworkLanguage string + FailOnDuplicateTestID bool + MutuallyExclusiveParsers []Parser + GenericParsers []Parser + FrameworkParsers map[v1.Framework][]Parser + IdentityRecipes map[string]v1.TestIdentityRecipe + Logger *zap.SugaredLogger +} + +func (c Config) Validate() error { + if c.ProvidedFrameworkKind != "" && c.ProvidedFrameworkLanguage == "" { + return errors.NewConfigurationError( + "Unable to determine test result format", + "You provided the test framework that produced the test result, but not the language. Captain needs "+ + "both options in order to interpret the results file correctly.", + "The test-framework language can be set using the --language flag. Alternatively, you can use "+ + "the Captain configuration file to permanently set the framework options for a test suite.", + ) + } + + if c.ProvidedFrameworkLanguage != "" && c.ProvidedFrameworkKind == "" { + return errors.NewConfigurationError( + "Unable to determine test result format", + "You provided the test-framework language that produced the test result, but not the framework name "+ + "itself. Captain needs both options in order to interpret the results file correctly.", + "The test framework can be set using the --framework flag. Alternatively, you can use "+ + "the Captain configuration file to permanently set the framework options for a test suite.", + ) + } + + if c.Logger == nil { + return errors.NewInternalError("No logger was provided") + } + + return nil +} + +func Parse(file fs.File, groupNumber int, cfg Config) (*v1.TestResults, error) { + if err := cfg.Validate(); err != nil { + return nil, errors.WithStack(err) + } + + var parsers []Parser + var coercedFramework *v1.Framework + if cfg.ProvidedFrameworkKind == "" && cfg.ProvidedFrameworkLanguage == "" { + parsers = append(parsers, cfg.MutuallyExclusiveParsers...) + parsers = append(parsers, cfg.GenericParsers...) + } else { + framework := v1.CoerceFramework(cfg.ProvidedFrameworkLanguage, cfg.ProvidedFrameworkKind) + + if framework.IsOther() { + cfg.Logger.Warnf( + "Could not find a suitable parser for %q and %q - Captain will fall back to generic parsers.", + cfg.ProvidedFrameworkLanguage, + cfg.ProvidedFrameworkKind, + ) + cfg.Logger.Warnln( + "If this fails, omit the `--language` and `--framework` flags to attempt parsing with all available parsers" + + " instead.", + ) + parsers = cfg.GenericParsers + } else { + parsers = append(parsers, cfg.FrameworkParsers[framework]...) + } + + coercedFramework = &framework + } + + results, err := parseWith(file, parsers, groupNumber, cfg) + if err != nil { + return nil, err + } + + if coercedFramework != nil { + results.Framework = *coercedFramework + } + + if results.Framework.IsOther() && !results.Framework.IsProvided() { + cfg.Logger.Warnf( + "We could not determine which framework produced your test results. " + + "For a better experience in Captain, specify both your --language and --framework.", + ) + } + + return results, nil +} + +func parseWith(file fs.File, parsers []Parser, groupNumber int, cfg Config) (*v1.TestResults, error) { + if len(parsers) == 0 { + return nil, errors.NewInternalError("No parsers were provided") + } + + parsedTestResults := make([]v1.TestResults, 0) + var firstParser Parser + for _, parser := range parsers { + if err := rewindFile(file); err != nil { + return nil, err + } + + parsedTestResult, err := parser.Parse(file) + if err != nil { + cfg.Logger.Debugf("%T was not capable of parsing the test results. Error: %v", parser, err) + continue + } + if parsedTestResult == nil { + return nil, errors.NewInternalError("%T did not error and did not return a test result", parser) + } + + cfg.Logger.Debugf("%T was capable of parsing the test results.", parser) + + if firstParser == nil { + firstParser = parser + } + parsedTestResults = append(parsedTestResults, *parsedTestResult) + } + + if len(parsedTestResults) == 0 { + return nil, errors.NewInputError("No parsers were capable of parsing the provided test results") + } + + finalResults := parsedTestResults[0] + cfg.Logger.Debugf("%T was ultimately responsible for parsing the test results", firstParser) + + duplicateTestIDs, err := checkIfTestIDsAreUnique(finalResults, cfg) + if err != nil { + return nil, err + } + if len(duplicateTestIDs) != 0 { + duplicateTestIDWarningMsg := fmt.Sprintf( + "Test result file %q contains two or more tests that share the same metadata:\n\n\t%s\n\n%s", + file.Name(), + strings.Join(duplicateTestIDs, "\n\t"), + "Please make sure test identifiers are unique.", + ) + if cfg.FailOnDuplicateTestID { + return nil, errors.NewDuplicateTestIDError("%s", duplicateTestIDWarningMsg) + } + cfg.Logger.Warn(duplicateTestIDWarningMsg) + } + + if err := rewindFile(file); err != nil { + return nil, err + } + buf, err := io.ReadAll(file) + if err != nil { + return nil, errors.NewSystemError("Unable to read file into buffer: %s", err) + } + if err := rewindFile(file); err != nil { + return nil, err + } + + // only set DerivedFrom for non-RWX parsers + if _, ok := firstParser.(RWXParser); !ok { + finalResults.DerivedFrom = []v1.OriginalTestResults{ + { + OriginalFilePath: file.Name(), + Contents: base64.StdEncoding.EncodeToString(buf), + GroupNumber: groupNumber, + }, + } + } + + return &finalResults, nil +} + +func checkIfTestIDsAreUnique(testResult v1.TestResults, cfg Config) ([]string, error) { + uniqueTestIdentifiers := make(map[string]struct{}) + uniqueTestMatchingIdentities := make(map[string]struct{}) + duplicateTestIDs := make([]string, 0) + identityRecipe, recipeFound := cfg.IdentityRecipes[testResult.Framework.String()] + + if !recipeFound && (cfg.ProvidedFrameworkKind != "" || cfg.ProvidedFrameworkLanguage != "") { + identityRecipe, recipeFound = cfg.IdentityRecipes[v1.CoerceFramework( + string(v1.FrameworkLanguageOther), + string(v1.FrameworkKindOther), + ).String()] + } + + for _, test := range testResult.Tests { + var id string + var err error + + // Check the identity based on the identity recipe for duplicates + if recipeFound { + id, err = test.Identify(identityRecipe) + if err != nil { + cfg.Logger.Warnf("Unable to construct identity from test: %s", err.Error()) + } else { + if _, ok := uniqueTestIdentifiers[id]; ok { + duplicateTestIDs = append(duplicateTestIDs, id) + } else { + uniqueTestIdentifiers[id] = struct{}{} + } + } + } + + // Check the identity used for matching for duplicates + identityForMatching := test.IdentityForMatching() + if _, ok := uniqueTestMatchingIdentities[identityForMatching]; ok { + if id != "" { + duplicateTestIDs = append(duplicateTestIDs, id) + } else { + duplicateTestIDs = append(duplicateTestIDs, test.Name) + } + } else { + uniqueTestMatchingIdentities[identityForMatching] = struct{}{} + } + } + + return duplicateTestIDs, nil +} + +func rewindFile(file fs.File) error { + if _, err := file.Seek(0, io.SeekStart); err != nil { + return errors.NewSystemError("Unable to rewind file: %s", err) + } + + return nil +} diff --git a/internal/captain/parsing/parse_test.go b/internal/captain/parsing/parse_test.go new file mode 100644 index 0000000..a77e374 --- /dev/null +++ b/internal/captain/parsing/parse_test.go @@ -0,0 +1,519 @@ +package parsing_test + +import ( + "encoding/base64" + "fmt" + "io" + "os" + "strings" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" + "go.uber.org/zap/zaptest/observer" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/mocks" + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +type SuccessfulParserOne struct{} + +func (p SuccessfulParserOne) Parse(testResults io.Reader) (*v1.TestResults, error) { + buf, err := io.ReadAll(testResults) + Expect(string(buf)).To(Equal("the fake contents to base64 encode")) + Expect(err).NotTo(HaveOccurred()) + one := "one" + return &v1.TestResults{Summary: v1.Summary{Tests: 1}, Framework: v1.NewOtherFramework(&one, &one)}, nil +} + +type SuccessfulParserTwo struct{} + +func (p SuccessfulParserTwo) Parse(testResults io.Reader) (*v1.TestResults, error) { + buf, err := io.ReadAll(testResults) + Expect(string(buf)).To(Equal("the fake contents to base64 encode")) + Expect(err).NotTo(HaveOccurred()) + two := "two" + return &v1.TestResults{Summary: v1.Summary{Tests: 2}, Framework: v1.NewOtherFramework(&two, &two)}, nil +} + +type ErrorParser struct{} + +func (p ErrorParser) Parse(testResults io.Reader) (*v1.TestResults, error) { + buf, err := io.ReadAll(testResults) + Expect(string(buf)).To(Equal("the fake contents to base64 encode")) + Expect(err).NotTo(HaveOccurred()) + return nil, errors.NewInternalError("could not parse") +} + +type NeitherErrorNorResultsParser struct{} + +func (p NeitherErrorNorResultsParser) Parse(_ io.Reader) (*v1.TestResults, error) { + return nil, nil +} + +var _ = Describe("Parse", func() { + var ( + logCore zapcore.Core + log *zap.SugaredLogger + recordedLogs *observer.ObservedLogs + file *mocks.File + ) + + BeforeEach(func() { + logCore, recordedLogs = observer.New(zapcore.DebugLevel) + log = zaptest.NewLogger(GinkgoT(), zaptest.WrapOptions( + zap.WrapCore(func(_ zapcore.Core) zapcore.Core { return logCore }), + )).Sugar() + file = new(mocks.File) + file.Reader = strings.NewReader("the fake contents to base64 encode") + file.MockName = func() string { return "some/path/to/file" } + }) + + It("is an error when no logger is provided", func() { + results, err := parsing.Parse( + file, + 1, + parsing.Config{}, + ) + + Expect(results).To(BeNil()) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(ContainSubstring("No logger was provided")) + }) + + It("is an error when only language is provided", func() { + results, err := parsing.Parse( + file, + 1, + parsing.Config{Logger: log, ProvidedFrameworkLanguage: "foo"}, + ) + + Expect(results).To(BeNil()) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(ContainSubstring("Unable to determine test result format")) + }) + + It("is an error when only kind is provided", func() { + results, err := parsing.Parse( + file, + 1, + parsing.Config{Logger: log, ProvidedFrameworkKind: "foo"}, + ) + + Expect(results).To(BeNil()) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(ContainSubstring("Unable to determine test result format")) + }) + + It("is an error when a parser returns neither a result nor an error", func() { + results, err := parsing.Parse( + file, + 1, + parsing.Config{ + MutuallyExclusiveParsers: []parsing.Parser{NeitherErrorNorResultsParser{}}, + Logger: log, + }, + ) + + Expect(results).To(BeNil()) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To( + ContainSubstring("NeitherErrorNorResultsParser did not error and did not return a test result"), + ) + }) + + It("is an error when no parsers can parse", func() { + results, err := parsing.Parse( + file, + 1, + parsing.Config{ + MutuallyExclusiveParsers: []parsing.Parser{ + ErrorParser{}, + ErrorParser{}, + ErrorParser{}, + }, + Logger: log, + }, + ) + + Expect(results).To(BeNil()) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To( + ContainSubstring("No parsers were capable of parsing the provided test results"), + ) + + logMessages := make([]string, 0) + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement( + ContainSubstring("ErrorParser was not capable of parsing the test results"), + )) + Expect(logMessages).NotTo(ContainElement( + ContainSubstring("ultimately responsible for parsing the test results"), + )) + }) + + It("returns the first test results with the base64 encoded content", func() { + results, err := parsing.Parse( + file, + 2, + parsing.Config{ + MutuallyExclusiveParsers: []parsing.Parser{ + SuccessfulParserTwo{}, + ErrorParser{}, + SuccessfulParserOne{}, + }, + Logger: log, + }, + ) + + Expect(results).NotTo(BeNil()) + Expect(*results.Framework.ProvidedKind).To(Equal("two")) + Expect(results.DerivedFrom).To(Equal( + []v1.OriginalTestResults{ + { + OriginalFilePath: "some/path/to/file", + Contents: base64.StdEncoding.EncodeToString([]byte("the fake contents to base64 encode")), + GroupNumber: 2, + }, + }, + )) + Expect(err).To(BeNil()) + + logMessages := make([]string, 0) + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement( + ContainSubstring("ErrorParser was not capable of parsing the test results"), + )) + Expect(logMessages).To(ContainElement( + ContainSubstring("SuccessfulParserOne was capable of parsing the test results."), + )) + Expect(logMessages).To(ContainElement( + ContainSubstring("SuccessfulParserTwo was capable of parsing the test results."), + )) + Expect(logMessages).To(ContainElement( + ContainSubstring("SuccessfulParserTwo was ultimately responsible for parsing the test results"), + )) + + // ensure it rewinds the file once done + buf, err := io.ReadAll(file) + Expect(string(buf)).To(Equal("the fake contents to base64 encode")) + Expect(err).NotTo(HaveOccurred()) + }) + + It("does not set DerivedFrom when parsing with the RWX parser", func() { + fixture, err := os.Open("../../test/fixtures/rwx/v1_not_derived.json") + Expect(err).ToNot(HaveOccurred()) + buf, err := io.ReadAll(fixture) + Expect(err).ToNot(HaveOccurred()) + + file = new(mocks.File) + file.Reader = strings.NewReader(string(buf)) + file.MockName = func() string { return "some/path/to/file" } + + results, err := parsing.Parse( + file, + 1, + parsing.Config{ + MutuallyExclusiveParsers: []parsing.Parser{parsing.RWXParser{}}, + Logger: log, + }, + ) + + Expect(results).NotTo(BeNil()) + Expect(results.DerivedFrom).To(BeNil()) + Expect(err).To(BeNil()) + }) + + Describe("when no language and kind are provided", func() { + It("uses the mutually exclusive and generic parsers to auto-detect framework", func() { + results, err := parsing.Parse( + file, + 1, + parsing.Config{ + ProvidedFrameworkLanguage: "", + ProvidedFrameworkKind: "", + MutuallyExclusiveParsers: []parsing.Parser{ + SuccessfulParserTwo{}, + ErrorParser{}, + }, + GenericParsers: []parsing.Parser{SuccessfulParserOne{}}, + Logger: log, + }, + ) + + Expect(err).NotTo(HaveOccurred()) + Expect(results).NotTo(BeNil()) + Expect(*results.Framework.ProvidedKind).To(Equal("two")) + + results, err = parsing.Parse( + file, + 1, + parsing.Config{ + ProvidedFrameworkLanguage: "", + ProvidedFrameworkKind: "", + MutuallyExclusiveParsers: []parsing.Parser{ + ErrorParser{}, + }, + GenericParsers: []parsing.Parser{SuccessfulParserOne{}}, + Logger: log, + }, + ) + + Expect(err).NotTo(HaveOccurred()) + Expect(results).NotTo(BeNil()) + Expect(*results.Framework.ProvidedKind).To(Equal("one")) + }) + + It("is an error when no parsers are provided", func() { + results, err := parsing.Parse( + file, + 1, + parsing.Config{ + ProvidedFrameworkLanguage: "", + ProvidedFrameworkKind: "", + Logger: log, + }, + ) + + Expect(results).To(BeNil()) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(ContainSubstring("No parsers were provided")) + }) + }) + + Describe("when an unknown language and kind are provided", func() { + It("uses the generic parsers and sets the provided language and kind", func() { + results, err := parsing.Parse( + file, + 1, + parsing.Config{ + ProvidedFrameworkLanguage: "bar", + ProvidedFrameworkKind: "foo", + MutuallyExclusiveParsers: []parsing.Parser{ + SuccessfulParserTwo{}, + ErrorParser{}, + }, + GenericParsers: []parsing.Parser{SuccessfulParserOne{}}, + Logger: log, + }, + ) + + Expect(err).NotTo(HaveOccurred()) + Expect(results).NotTo(BeNil()) + Expect(results.Framework.IsOther()).To(Equal(true)) + Expect(*results.Framework.ProvidedLanguage).To(Equal("bar")) + Expect(*results.Framework.ProvidedKind).To(Equal("foo")) + }) + + It("is an error when no parsers are provided", func() { + results, err := parsing.Parse( + file, + 1, + parsing.Config{ + ProvidedFrameworkLanguage: "bar", + ProvidedFrameworkKind: "foo", + Logger: log, + }, + ) + + Expect(results).To(BeNil()) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(ContainSubstring("No parsers were provided")) + }) + }) + + Describe("when a known language and kind are provided", func() { + It("uses the framework parsers", func() { + results, err := parsing.Parse( + file, + 1, + parsing.Config{ + ProvidedFrameworkLanguage: "RuBy", + ProvidedFrameworkKind: "RspEc", + FrameworkParsers: map[v1.Framework][]parsing.Parser{ + v1.RubyRSpecFramework: {SuccessfulParserOne{}}, + v1.JavaScriptCypressFramework: {SuccessfulParserTwo{}}, + }, + Logger: log, + }, + ) + + Expect(err).NotTo(HaveOccurred()) + Expect(results).NotTo(BeNil()) + Expect(results.Summary.Tests).To(Equal(1)) + Expect(results.Framework).To(Equal(v1.RubyRSpecFramework)) + + results, err = parsing.Parse( + file, + 1, + parsing.Config{ + ProvidedFrameworkLanguage: "javascript", + ProvidedFrameworkKind: "cypress", + FrameworkParsers: map[v1.Framework][]parsing.Parser{ + v1.RubyRSpecFramework: {SuccessfulParserOne{}}, + v1.JavaScriptCypressFramework: {SuccessfulParserTwo{}}, + }, + Logger: log, + }, + ) + + Expect(err).NotTo(HaveOccurred()) + Expect(results).NotTo(BeNil()) + Expect(results.Summary.Tests).To(Equal(2)) + Expect(results.Framework).To(Equal(v1.JavaScriptCypressFramework)) + }) + + It("is an error when no parsers are provided", func() { + results, err := parsing.Parse( + file, + 1, + parsing.Config{ + ProvidedFrameworkLanguage: "RspEc", + ProvidedFrameworkKind: "RuBy", + Logger: log, + }, + ) + + Expect(results).To(BeNil()) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(ContainSubstring("No parsers were provided")) + }) + }) + + Describe("when the test results contain duplicate entries", func() { + It("emits a warning", func() { + fixture, err := os.Open("../../test/fixtures/jest_with_duplicates.json") + Expect(err).ToNot(HaveOccurred()) + + results, err := parsing.Parse( + fixture, + 1, + parsing.Config{ + ProvidedFrameworkLanguage: "Javascript", + ProvidedFrameworkKind: "Jest", + FrameworkParsers: map[v1.Framework][]parsing.Parser{ + v1.JavaScriptJestFramework: {parsing.JavaScriptJestParser{}}, + }, + Logger: log, + }, + ) + + Expect(err).NotTo(HaveOccurred()) + Expect(results).NotTo(BeNil()) + Expect(results.Summary.Tests).To(Equal(20)) + Expect(results.Framework).To(Equal(v1.JavaScriptJestFramework)) + + logMessages := make([]string, 0) + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement(ContainSubstring(fmt.Sprintf( + "Test result file %q contains two or more tests that share the same metadata", + "../../test/fixtures/jest_with_duplicates.json", + )))) + }) + + It("returns an error when configured to fail hard", func() { + fixture, err := os.Open("../../test/fixtures/jest_with_duplicates.json") + Expect(err).ToNot(HaveOccurred()) + + results, err := parsing.Parse( + fixture, + 1, + parsing.Config{ + ProvidedFrameworkLanguage: "Javascript", + ProvidedFrameworkKind: "Jest", + FrameworkParsers: map[v1.Framework][]parsing.Parser{ + v1.JavaScriptJestFramework: {parsing.JavaScriptJestParser{}}, + }, + FailOnDuplicateTestID: true, + Logger: log, + }, + ) + + Expect(results).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf( + "Test result file %q contains two or more tests that share the same metadata", + "../../test/fixtures/jest_with_duplicates.json", + ))) + }) + }) + + Describe("when the test results contain tests that share the same identity", func() { + It("it succeeds if identity recipes are not provided", func() { + fixture, err := os.Open("../../test/fixtures/mocha_with_duplicates.json") + Expect(err).ToNot(HaveOccurred()) + + results, err := parsing.Parse( + fixture, + 1, + parsing.Config{ + ProvidedFrameworkLanguage: "Javascript", + ProvidedFrameworkKind: "Mocha", + FrameworkParsers: map[v1.Framework][]parsing.Parser{ + v1.JavaScriptMochaFramework: {parsing.JavaScriptMochaParser{}}, + }, + Logger: log, + FailOnDuplicateTestID: true, + }, + ) + + Expect(err).NotTo(HaveOccurred()) + Expect(results).NotTo(BeNil()) + Expect(results.Summary.Tests).To(Equal(10)) + Expect(results.Framework).To(Equal(v1.JavaScriptMochaFramework)) + }) + + It("it warns if identity recipes are provided", func() { + fixture, err := os.Open("../../test/fixtures/mocha_with_duplicates.json") + Expect(err).ToNot(HaveOccurred()) + + results, err := parsing.Parse( + fixture, + 1, + parsing.Config{ + ProvidedFrameworkLanguage: "Javascript", + ProvidedFrameworkKind: "Mocha", + FrameworkParsers: map[v1.Framework][]parsing.Parser{ + v1.JavaScriptMochaFramework: {parsing.JavaScriptMochaParser{}}, + }, + Logger: log, + IdentityRecipes: map[string]v1.TestIdentityRecipe{ + v1.JavaScriptMochaFramework.String(): { + Components: []string{"file", "description"}, + Strict: true, + }, + }, + }, + ) + + Expect(err).NotTo(HaveOccurred()) + Expect(results).NotTo(BeNil()) + Expect(results.Summary.Tests).To(Equal(10)) + Expect(results.Framework).To(Equal(v1.JavaScriptMochaFramework)) + + logMessages := make([]string, 0) + for _, log := range recordedLogs.All() { + logMessages = append(logMessages, log.Message) + } + + Expect(logMessages).To(ContainElement(ContainSubstring(fmt.Sprintf( + "Test result file %q contains two or more tests that share the same metadata", + "../../test/fixtures/mocha_with_duplicates.json", + )))) + }) + }) +}) diff --git a/internal/captain/parsing/parser.go b/internal/captain/parsing/parser.go new file mode 100644 index 0000000..da7fae1 --- /dev/null +++ b/internal/captain/parsing/parser.go @@ -0,0 +1,11 @@ +package parsing + +import ( + "io" + + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type Parser interface { + Parse(io.Reader) (*v1.TestResults, error) +} diff --git a/internal/captain/parsing/parsing_suite_test.go b/internal/captain/parsing/parsing_suite_test.go new file mode 100644 index 0000000..c0685ef --- /dev/null +++ b/internal/captain/parsing/parsing_suite_test.go @@ -0,0 +1,15 @@ +package parsing_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestTesting(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "Parsing Suite") +} diff --git a/internal/captain/parsing/phpunit_parser.go b/internal/captain/parsing/phpunit_parser.go new file mode 100644 index 0000000..bc9b076 --- /dev/null +++ b/internal/captain/parsing/phpunit_parser.go @@ -0,0 +1,169 @@ +package parsing + +import ( + "encoding/xml" + "fmt" + "io" + "math" + "regexp" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type PHPUnitParser struct{} + +type PHPUnitFailure struct { + Type *string `xml:"type,attr"` + Contents *string `xml:",chardata"` +} + +type PHPUnitSkipped struct{} + +type PHPUnitTestCase struct { + Class string `xml:"class,attr"` + ClassName string `xml:"classname,attr"` + Error *PHPUnitFailure `xml:"error"` + Failure *PHPUnitFailure `xml:"failure"` + Name string `xml:"name,attr"` + Skipped *PHPUnitSkipped `xml:"skipped"` + Time float64 `xml:"time,attr"` + File string `xml:"file,attr"` + Line int `xml:"line,attr"` + + XMLName xml.Name `xml:"testcase"` +} + +type PHPUnitTestSuite struct { + Assertions int `xml:"assertions,attr"` + Errors int `xml:"errors,attr"` + Failures int `xml:"failures,attr"` + File *string `xml:"file,attr"` + Name string `xml:"name,attr"` + Skipped int `xml:"skipped,attr"` + TestCases []PHPUnitTestCase `xml:"testcase"` + Tests *int `xml:"tests,attr"` + TestSuites []PHPUnitTestSuite `xml:"testsuite"` + Time float64 `xml:"time,attr"` + Warnings int `xml:"warnings,attr"` + + XMLName xml.Name `xml:"testsuite"` +} + +type PHPUnitTestResults struct { + TestSuites []PHPUnitTestSuite `xml:"testsuite"` + XMLName xml.Name `xml:"testsuites"` +} + +var phpUnitNewlineRegexp = regexp.MustCompile(`\r?\n`) + +func (p PHPUnitParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testResults PHPUnitTestResults + + if err := xml.NewDecoder(data).Decode(&testResults); err != nil { + return nil, errors.NewInputError("Unable to parse test results as XML: %s", err) + } + + tests := make([]v1.Test, 0) + for _, suite := range testResults.TestSuites { + foundTests, err := p.testsWithinSuite(suite) + if err != nil { + return nil, err + } + tests = append(tests, foundTests...) + } + + return v1.NewTestResults( + v1.PHPUnitFramework, + tests, + nil, + ), nil +} + +func (p PHPUnitParser) testsWithinSuite(suite PHPUnitTestSuite) ([]v1.Test, error) { + nestedTests := make([]v1.Test, 0) + for _, nestedSuite := range suite.TestSuites { + foundTests, err := p.testsWithinSuite(nestedSuite) + if err != nil { + return nil, err + } + nestedTests = append(nestedTests, foundTests...) + } + + tests := make([]v1.Test, 0) + for _, testCase := range suite.TestCases { + name := fmt.Sprintf("%s::%s", testCase.Class, testCase.Name) + lineage := []string{testCase.Class, testCase.Name} + duration := time.Duration(math.Round(testCase.Time * float64(time.Second))) + + risky := false + var status v1.TestStatus + switch { + case testCase.Failure != nil: + determinedStatus, wasRisky := p.newFailedTestStatus(*testCase.Failure) + + risky = wasRisky + if wasRisky { + status = v1.NewSuccessfulTestStatus() + } else { + status = determinedStatus + } + case testCase.Error != nil: + determinedStatus, wasRisky := p.newFailedTestStatus(*testCase.Error) + + risky = wasRisky + if wasRisky { + status = v1.NewSuccessfulTestStatus() + } else { + status = determinedStatus + } + case testCase.Skipped != nil: + status = v1.NewSkippedTestStatus(nil) + default: + status = v1.NewSuccessfulTestStatus() + } + + line := testCase.Line + location := &v1.Location{File: testCase.File, Line: &line} + + tests = append( + tests, + v1.Test{ + Name: name, + Lineage: lineage, + Location: location, + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: map[string]any{ + "class": testCase.Class, + "name": testCase.Name, + "risky": risky, + }, + Status: status, + }, + }, + ) + } + + tests = append(tests, nestedTests...) + return tests, nil +} + +// returns the determined failed status and whether it was risky or not +func (p PHPUnitParser) newFailedTestStatus(failure PHPUnitFailure) (v1.TestStatus, bool) { + failureException := failure.Type + risky := failureException != nil && *failureException == "PHPUnit\\Framework\\RiskyTestError" + + var lines []string + if failure.Contents != nil { + lines = phpUnitNewlineRegexp.Split(*failure.Contents, -1) + } + if len(lines) < 4 { + return v1.NewFailedTestStatus(nil, failureException, nil), risky + } + + message, backtracePart := lines[1], lines[3] + + return v1.NewFailedTestStatus(&message, failureException, []string{backtracePart}), risky +} diff --git a/internal/captain/parsing/phpunit_parser_test.go b/internal/captain/parsing/phpunit_parser_test.go new file mode 100644 index 0000000..b171d19 --- /dev/null +++ b/internal/captain/parsing/phpunit_parser_test.go @@ -0,0 +1,59 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("PHPUnitParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/phpunit.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.PHPUnitParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses empty files", func() { + testResults, err := parsing.PHPUnitParser{}.Parse(strings.NewReader(``)) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults.Tests).To(HaveLen(0)) + + testResults, err = parsing.PHPUnitParser{}.Parse( + strings.NewReader(``), + ) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults.Tests).To(HaveLen(0)) + }) + + It("errors on malformed XML", func() { + testResults, err := parsing.PHPUnitParser{}.Parse(strings.NewReader(``)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as XML")) + Expect(testResults).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/python_pytest_parser.go b/internal/captain/parsing/python_pytest_parser.go new file mode 100644 index 0000000..c31aad5 --- /dev/null +++ b/internal/captain/parsing/python_pytest_parser.go @@ -0,0 +1,194 @@ +package parsing + +import ( + "encoding/json" + "fmt" + "io" + "math" + "sort" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type PythonPytestParser struct{} + +// This is a subset of the failed longrepr because it's quite challenging to model in Go +type PythonPytestFailedLongrepr struct { + Reprcrash struct { + Path string `json:"path"` + Lineno int `json:"lineno"` + Message string `json:"message"` + } `json:"reprcrash"` +} + +type PythonPytestSkippedLongrepr []any + +type PythonPytestTestResult struct { + Nodeid string `json:"nodeid"` + Location []any `json:"location"` // ["test_top_level.py", 0, "test_top_level_passing"], + Keywords map[string]int `json:"keywords"` + Outcome string `json:"outcome"` // failed, passed, skipped + Longrepr json.RawMessage `json:"longrepr"` + When string `json:"when"` + UserProperties [][]any `json:"user_properties"` + Sections []any `json:"sections"` // unsure how this is used, moving on for now + Duration float64 `json:"duration"` // in seconds + ReportType *string `json:"$report_type"` + + // only when run in parallel w/ pytest-xdist + ItemIndex int `json:"item_index"` + WorkerID string `json:"worker_id"` + TestrunUID string `json:"testrun_uid"` + Node string `json:"node"` +} + +func (p PythonPytestParser) Parse(data io.Reader) (*v1.TestResults, error) { + decoder := json.NewDecoder(data) + didSeeSessionStart := false + testsByNodeid := map[string]v1.Test{} + + for decoder.More() { + var item json.RawMessage + if err := decoder.Decode(&item); err != nil { + return nil, errors.NewInputError("Unable to parse test results as JSON: %s", err) + } + + var testResult PythonPytestTestResult + if err := json.Unmarshal(item, &testResult); err != nil { + return nil, errors.NewInputError("Unable to parse test results as JSON: %s", err) + } + if testResult.ReportType == nil { + return nil, errors.NewInputError("Test results do not look like pytest resultlog (Missing $report_type)") + } + + if *testResult.ReportType == "SessionStart" { + didSeeSessionStart = true + continue + } + if *testResult.ReportType != "TestReport" { + continue + } + + file := testResult.Location[0].(string) + line := int(testResult.Location[1].(float64)) + location := v1.Location{ + File: file, + Line: &line, + } + duration := time.Duration(math.Round(testResult.Duration * float64(time.Second))) + status, err := p.statusOf(testResult) + if err != nil { + return nil, err + } + userProperties := map[string]any{} + for _, userProperty := range testResult.UserProperties { + key, value := userProperty[0], userProperty[1] + userProperties[key.(string)] = value + } + + test, ok := testsByNodeid[testResult.Nodeid] + if ok { + newDuration := *test.Attempt.Duration + duration + test.Attempt.Duration = &newDuration + test.Attempt.Meta = map[string]any{"user_properties": userProperties} + + // when the test is skipped, the setup is not run and the teardown is always passing, so we can + // keep the original status + if test.Attempt.Status.Kind == v1.TestStatusSkipped { + continue + } + + // when the test has failed, we don't want to overwrite it with a success + if test.Attempt.Status.Kind == v1.TestStatusFailed { + continue + } + + test.Attempt.Status = *status + } else { + name := strings.TrimPrefix(testResult.Nodeid, fmt.Sprintf("%v::", location.File)) + test = v1.Test{ + ID: &testResult.Nodeid, + Name: name, + Lineage: strings.Split(name, "::"), + Location: &location, + Attempt: v1.TestAttempt{ + Duration: &duration, + Status: *status, + Meta: map[string]any{"user_properties": userProperties}, + }, + } + } + testsByNodeid[testResult.Nodeid] = test + } + if !didSeeSessionStart { + return nil, errors.NewInputError("Test results do not look like pytest resultlog (Missing SessionStart)") + } + + tests := make([]v1.Test, len(testsByNodeid)) + i := 0 + for _, test := range testsByNodeid { + tests[i] = test + i++ + } + + // For determinism + sort.Slice(tests, func(i, j int) bool { return *tests[i].ID < *tests[j].ID }) + + return v1.NewTestResults( + v1.PythonPytestFramework, + tests, + nil, + ), nil +} + +func (p PythonPytestParser) statusOf(testResult PythonPytestTestResult) (*v1.TestStatus, error) { + var status v1.TestStatus + switch testResult.Outcome { + case "failed": + _, wasxfail := testResult.Keywords["xfail"] + + if wasxfail { + message := "Unexpectedly passed" + status = v1.NewFailedTestStatus(&message, nil, nil) + break + } + + var longrepr PythonPytestFailedLongrepr + err := json.Unmarshal(testResult.Longrepr, &longrepr) + if err != nil { + fmt.Printf("%s", testResult.Longrepr) + return nil, errors.Wrap(err, "Unable to parse skipped test longrepr") + } + + message := longrepr.Reprcrash.Message + backtrace := []string{fmt.Sprintf("%v:%v", longrepr.Reprcrash.Path, longrepr.Reprcrash.Lineno)} + status = v1.NewFailedTestStatus(&message, nil, backtrace) + case "passed": + status = v1.NewSuccessfulTestStatus() + case "skipped": + _, wasxfail := testResult.Keywords["xfail"] + + if wasxfail { + message := "Expected failure was skipped" + status = v1.NewSkippedTestStatus(&message) + break + } + + var longrepr PythonPytestSkippedLongrepr + err := json.Unmarshal(testResult.Longrepr, &longrepr) + if err != nil { + fmt.Printf("%s", testResult.Longrepr) + return nil, errors.Wrap(err, "Unable to parse skipped test longrepr") + } + + skippedMessage := strings.TrimPrefix(longrepr[2].(string), "Skipped: ") + status = v1.NewSkippedTestStatus(&skippedMessage) + default: + return nil, errors.NewInputError("Unexpected test outcome %q", testResult.Outcome) + } + + return &status, nil +} diff --git a/internal/captain/parsing/python_pytest_parser_test.go b/internal/captain/parsing/python_pytest_parser_test.go new file mode 100644 index 0000000..2e9c0ab --- /dev/null +++ b/internal/captain/parsing/python_pytest_parser_test.go @@ -0,0 +1,67 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("PythonPytestParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/pytest_reportlog.jsonl") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.PythonPytestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("errors on malformed JSON", func() { + testResults, err := parsing.PythonPytestParser{}.Parse(strings.NewReader(`{"pytest_version":`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as JSON")) + Expect(testResults).To(BeNil()) + }) + + It("errors JSON that doesn't look like pytest", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.PythonPytestParser{}.Parse(strings.NewReader(`{}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring( + "Test results do not look like pytest resultlog (Missing $report_type)", + )) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.PythonPytestParser{}.Parse(strings.NewReader(`{"$report_type": "WarningMessage"}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring( + "Test results do not look like pytest resultlog (Missing SessionStart)", + )) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.PythonPytestParser{}.Parse(strings.NewReader( + ` + {"pytest_version": "1.0.0", "$report_type": "SessionStart"} + {"not valid" + `, + )) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as JSON")) + Expect(testResults).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/python_unittest_parser.go b/internal/captain/parsing/python_unittest_parser.go new file mode 100644 index 0000000..67e0ae6 --- /dev/null +++ b/internal/captain/parsing/python_unittest_parser.go @@ -0,0 +1,151 @@ +package parsing + +import ( + "encoding/xml" + "io" + "math" + "regexp" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type PythonUnitTestParser struct{} + +type PythonUnitTestFailure struct { + Type *string `xml:"type,attr"` + Message *string `xml:"message,attr"` + CDataContents *string `xml:",cdata"` +} + +type PythonUnitTestSkipped struct { + Message *string `xml:"message,attr"` + Type *string `xml:"type,attr"` +} + +type PythonUnitTestTestCase struct { + ClassName string `xml:"classname,attr"` + Error *PythonUnitTestFailure `xml:"error"` + Failure *PythonUnitTestFailure `xml:"failure"` + File string `xml:"file,attr"` + Line int `xml:"line,attr"` + Name string `xml:"name,attr"` + Skipped *PythonUnitTestSkipped `xml:"skipped"` + Time float64 `xml:"time,attr"` + Timestamp string `xml:"timestamp,attr"` + + XMLName xml.Name `xml:"testcase"` +} + +type PythonUnitTestProperty struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} + +type PythonUnitTestTestSuite struct { + Errors int `xml:"errors,attr"` + Failures int `xml:"failures,attr"` + File string `xml:"file,attr"` + Name string `xml:"name,attr"` + Skipped int `xml:"skipped,attr"` + TestCases []PythonUnitTestTestCase `xml:"testcase"` + Tests *int `xml:"tests,attr"` + Time float64 `xml:"time,attr"` + Timestamp string `xml:"timestamp,attr"` + + TestSuites *[]PythonUnitTestTestSuite `xml:"testsuite"` +} + +var PythonUnitTestNewlineRegexp = regexp.MustCompile(`\r?\n`) + +func (p PythonUnitTestParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testResults PythonUnitTestTestSuite + + if err := xml.NewDecoder(data).Decode(&testResults); err != nil { + return nil, errors.NewInputError("Unable to parse test results as XML: %s", err) + } + + var testSuites []PythonUnitTestTestSuite + if testResults.TestSuites == nil { + testSuites = []PythonUnitTestTestSuite{testResults} + } else { + testSuites = *testResults.TestSuites + } + + if len(testSuites) > 0 && testSuites[0].Tests == nil { + return nil, errors.NewInputError("The test suites in the XML do not appear to match unittest XML") + } + + tests := make([]v1.Test, 0) + for _, testSuite := range testSuites { + for _, testCase := range testSuite.TestCases { + name := testCase.ClassName + "." + testCase.Name + duration := time.Duration(math.Round(testCase.Time * float64(time.Second))) + + var status v1.TestStatus + switch { + case testCase.Failure != nil: + status = p.NewFailedTestStatus(*testCase.Failure) + case testCase.Error != nil: + status = p.NewFailedTestStatus(*testCase.Error) + case testCase.Skipped != nil: + status = v1.NewSkippedTestStatus(testCase.Skipped.Message) + default: + status = v1.NewSuccessfulTestStatus() + } + + testCase := testCase + location := &v1.Location{File: testCase.File, Line: &testCase.Line} + startedAt, err := time.Parse("2006-01-02T15:04:05", testCase.Timestamp) + var finishedAt time.Time + if err == nil { + finishedAt = startedAt.Add(duration) + } + + tests = append( + tests, + v1.Test{ + Name: name, + Location: location, + Lineage: []string{testCase.ClassName, testCase.Name}, + Attempt: v1.TestAttempt{ + Duration: &duration, + Status: status, + StartedAt: &startedAt, + FinishedAt: &finishedAt, + }, + }, + ) + } + } + + return v1.NewTestResults( + v1.PythonUnitTestFramework, + tests, + nil, + ), nil +} + +func (p PythonUnitTestParser) NewFailedTestStatus(failure PythonUnitTestFailure) v1.TestStatus { + failureMessage := failure.Message + failureException := failure.Type + + var lines []string + switch { + case failure.CDataContents != nil: + lines = PythonUnitTestNewlineRegexp.Split(*failure.CDataContents, -1) + default: + lines = nil + } + if len(lines) == 0 { + return v1.NewFailedTestStatus(failureMessage, failureException, nil) + } + + backtrace := make([]string, 0) + for _, line := range lines { + backtrace = append(backtrace, strings.TrimSpace(line)) + } + return v1.NewFailedTestStatus(failureMessage, failureException, backtrace) +} diff --git a/internal/captain/parsing/python_unittest_parser_test.go b/internal/captain/parsing/python_unittest_parser_test.go new file mode 100644 index 0000000..5d2ad89 --- /dev/null +++ b/internal/captain/parsing/python_unittest_parser_test.go @@ -0,0 +1,84 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("PythonUnitTestParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/unittest.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.PythonUnitTestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("handles files with multiple test suites", func() { + testResults, err := parsing.PythonUnitTestParser{}.Parse(strings.NewReader( + ` + + + + + + + + + `, + )) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("errors on malformed XML", func() { + testResults, err := parsing.PythonUnitTestParser{}.Parse(strings.NewReader(``)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("The test suites in the XML do not appear to match unittest XML")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.PythonUnitTestParser{}.Parse( + strings.NewReader(``), + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring("The test suites in the XML do not appear to match unittest XML"), + ) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.PythonUnitTestParser{}.Parse( + strings.NewReader(``), + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring("The test suites in the XML do not appear to match unittest XML"), + ) + Expect(testResults).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/ruby_cucumber_parser.go b/internal/captain/parsing/ruby_cucumber_parser.go new file mode 100644 index 0000000..7c1c26f --- /dev/null +++ b/internal/captain/parsing/ruby_cucumber_parser.go @@ -0,0 +1,245 @@ +package parsing + +import ( + "encoding/json" + "io" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type RubyCucumberParser struct{} + +// relevant: the ruby implementation (our reference implementation) is more stringent than the schema +// schema is here: https://github.com/cucumber/cucumber-json-converter/blob/6281588f716cfbcf98b8e107e6f8650f5d92b53c/src/cucumber-ruby/RubyCucumberRubySchema.ts + +// https://github.com/cucumber/cucumber-ruby/blob/b0c5eff4c3a6675f791692b677126e99788165b5/lib/cucumber/formatter/json.rb#L253-L265 +// maps to a Lineage +type RubyCucumberFeature struct { + ID string `json:"id"` + URI string `json:"uri"` + Keyword string `json:"keyword"` + Name string `json:"name"` + Description string `json:"description"` + Line int `json:"line"` + Tags []RubyCucumberTag `json:"tags"` + Elements []RubyCucumberElement `json:"elements"` +} + +type RubyCucumberTag struct { + Name string `json:"name"` + Line *int `json:"line"` +} + +// https://github.com/cucumber/cucumber-ruby/blob/b0c5eff4c3a6675f791692b677126e99788165b5/lib/cucumber/formatter/json.rb#L280-L288 +// maps to a Lineage +type RubyCucumberElement struct { + ID *string `json:"id"` + Keyword string `json:"keyword"` + Name string `json:"name"` + Description string `json:"description"` + Line int `json:"line"` + Type string `json:"type"` + Tags []RubyCucumberTag `json:"tags"` + Steps []RubyCucumberStep `json:"steps"` + Before []RubyCucumberHook `json:"before"` + After []RubyCucumberHook `json:"after"` +} + +type RubyCucumberHook struct { + Match RubyCucumberMatch `json:"match"` + Result *RubyCucumberResult `json:"result"` +} + +// https://github.com/cucumber/cucumber-ruby/blob/b0c5eff4c3a6675f791692b677126e99788165b5/lib/cucumber/formatter/json.rb#L174-L178 +// this is missing fields: doc_string, rows, src, mime_type, label +// see: +// https://github.com/cucumber/cucumber-ruby/blob/b0c5eff4c3a6675f791692b677126e99788165b5/lib/cucumber/formatter/json.rb#202 +// maps to a Test +type RubyCucumberStep struct { + Keyword string `json:"keyword"` + Name string `json:"name"` + Line int `json:"line"` + Match *RubyCucumberMatch `json:"match"` + Result *RubyCucumberResult `json:"result"` +} + +// https://github.com/cucumber/cucumber-ruby/blob/b0c5eff4c3a6675f791692b677126e99788165b5/lib/cucumber/formatter/json.rb#L213-L215 +type RubyCucumberMatch struct { + Location string `json:"location"` +} + +// https://github.com/cucumber/cucumber-ruby/blob/b0c5eff4c3a6675f791692b677126e99788165b5/lib/cucumber/formatter/json.rb#L217-L224 +type RubyCucumberResult struct { + Status string `json:"status"` + Duration *int `json:"duration"` + ErrorMessage *string `json:"error_message"` // this is required if status is failed +} + +func (p RubyCucumberParser) Parse(data io.Reader) (*v1.TestResults, error) { + var cucumberFeatures []RubyCucumberFeature + + if err := json.NewDecoder(data).Decode(&cucumberFeatures); err != nil { + return nil, errors.NewInputError("Unable to parse test results as JSON: %s", err) + } + if len(cucumberFeatures) == 0 { + return nil, errors.NewInputError("No test results were found in the JSON") + } + + foundOneResult := false +outer: + for _, feature := range cucumberFeatures { + for _, element := range feature.Elements { + for _, step := range element.Steps { + if step.Result != nil { + foundOneResult = true + break outer + } + } + + for _, before := range element.Before { + if before.Result != nil { + foundOneResult = true + break outer + } + } + + for _, after := range element.After { + if after.Result != nil { + foundOneResult = true + break outer + } + } + } + } + + if !foundOneResult { + return nil, errors.NewInputError("Found features, but no results in the JSON") + } + + tests := make([]v1.Test, 0) + otherErrors := make([]v1.OtherError, 0) + + // https://github.com/cucumber/cucumber-ruby/blob/d9d6f380c77b79c3670fa8f1d620d7b57f42b3ae/lib/cucumber/formatter/usage.rb#L141 + // failed, then skipped, then pending, then undefined, then passed + priority := map[string]int{ + "failed": 5, + "skipped": 4, + "pending": 3, + "undefined": 2, + "passed": 1, + } + + for _, feature := range cucumberFeatures { + for i := 0; i < len(feature.Elements); i++ { + element := feature.Elements[i] + + // Merge Background into the next Scenario + if element.Type == "background" { + if i+1 >= len(feature.Elements) || feature.Elements[i+1].Type != "scenario" { + return nil, errors.NewInputError("Background must be followed by a Scenario in feature '%s'", feature.Name) + } + + nextElement := &feature.Elements[i+1] + + nextElement.Steps = append(element.Steps, nextElement.Steps...) + nextElement.Before = append(element.Before, nextElement.Before...) + nextElement.After = append(element.After, nextElement.After...) + continue + } + + duration := time.Duration(0) + allResults := make([]RubyCucumberResult, 0) + + for _, hook := range element.Before { + if hook.Result.Duration != nil { + duration += time.Duration(*hook.Result.Duration * int(time.Nanosecond)) + } + + if hook.Result == nil { + break + } + + allResults = append(allResults, *hook.Result) + } + + for _, step := range element.Steps { + if step.Result.Duration != nil { + duration += time.Duration(*step.Result.Duration * int(time.Nanosecond)) + } + + if step.Result == nil { + break + } + + allResults = append(allResults, *step.Result) + } + + for _, hook := range element.After { + if hook.Result.Duration != nil { + duration += time.Duration(*hook.Result.Duration * int(time.Nanosecond)) + } + + if hook.Result == nil { + break + } + + allResults = append(allResults, *hook.Result) + } + + var stepStatus string + var status v1.TestStatus + + for _, result := range allResults { + if _, ok := priority[result.Status]; !ok { + return nil, errors.NewInputError("Unexpected status %v", result.Status) + } + + if stepStatus == "" || priority[stepStatus] < priority[result.Status] { + stepStatus = result.Status + switch result.Status { + case "passed": + status = v1.NewSuccessfulTestStatus() + case "failed": + status = v1.NewFailedTestStatus(result.ErrorMessage, nil, nil) + case "skipped": + status = v1.NewSkippedTestStatus(result.ErrorMessage) + case "undefined": + status = v1.NewTodoTestStatus(result.ErrorMessage) + case "pending": + status = v1.NewTodoTestStatus(result.ErrorMessage) + default: + return nil, errors.NewInputError("Unexpected status %v", result.Status) + } + } + } + + location := v1.Location{File: feature.URI, Line: &element.Line} + attempt := v1.TestAttempt{ + Duration: &duration, + Status: status, + Meta: map[string]any{"tags": element.Tags}, + } + + lineage := []string{feature.Name, element.Name} + tests = append( + tests, + v1.Test{ + Name: strings.Join(lineage, " > "), + Lineage: lineage, + Location: &location, + Attempt: attempt, + PastAttempts: nil, + }, + ) + } + } + + return v1.NewTestResults( + v1.RubyCucumberFramework, + tests, + otherErrors, + ), nil +} diff --git a/internal/captain/parsing/ruby_cucumber_parser_test.go b/internal/captain/parsing/ruby_cucumber_parser_test.go new file mode 100644 index 0000000..1778cb5 --- /dev/null +++ b/internal/captain/parsing/ruby_cucumber_parser_test.go @@ -0,0 +1,150 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("RubyCucumberParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/cucumber/integration.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyCucumberParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses the sample file that uses background", func() { + fixture, err := os.Open("../../test/fixtures/cucumber/background_integration.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyCucumberParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses a passing element", func() { + fixture, err := os.Open("../../test/fixtures/cucumber/passing.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyCucumberParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses a failing element", func() { + fixture, err := os.Open("../../test/fixtures/cucumber/failing.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyCucumberParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses a pending element", func() { + fixture, err := os.Open("../../test/fixtures/cucumber/pending.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyCucumberParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses a skipped element", func() { + fixture, err := os.Open("../../test/fixtures/cucumber/skipped.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyCucumberParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses a undefined element", func() { + fixture, err := os.Open("../../test/fixtures/cucumber/undefined.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyCucumberParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses an element with a failing before hook", func() { + fixture, err := os.Open("../../test/fixtures/cucumber/before_fail.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyCucumberParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses an element with a failing after hook", func() { + fixture, err := os.Open("../../test/fixtures/cucumber/after_fail.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyCucumberParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("errors on malformed JSON", func() { + testResults, err := parsing.RubyCucumberParser{}.Parse(strings.NewReader(`{`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as JSON")) + Expect(testResults).To(BeNil()) + }) + + It("errors on JSON that doesn't look like Cucumber", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.RubyCucumberParser{}.Parse(strings.NewReader(`[]`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No test results were found in the JSON")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.RubyCucumberParser{}.Parse(strings.NewReader(` + [ + { + "id": "rule-sample", + "uri": "features/rule.feature", + "keyword": "Feature", + "name": "Rule Sample", + "description": "", + "line": 2 + } + ]`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Found features, but no results in the JSON")) + Expect(testResults).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/ruby_minitest_parser.go b/internal/captain/parsing/ruby_minitest_parser.go new file mode 100644 index 0000000..2efa08b --- /dev/null +++ b/internal/captain/parsing/ruby_minitest_parser.go @@ -0,0 +1,155 @@ +package parsing + +import ( + "encoding/xml" + "fmt" + "io" + "math" + "regexp" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +// Parses https://github.com/minitest-reporters/minitest-reporters/blob/73eea31b1e8b6af88c87f969cfa464d917f00cbb/lib/minitest/reporters/junit_reporter.rb#L16 +type RubyMinitestParser struct{} + +type RubyMinitestFailure struct { + Contents *string `xml:",chardata"` + Message *string `xml:"message,attr"` + Type *string `xml:"type,attr"` +} + +type RubyMinitestSkipped struct{} + +type RubyMinitestTestCase struct { + Assertions int `xml:"assertions,attr"` + ClassName string `xml:"classname,attr"` + Error *RubyMinitestFailure `xml:"error"` + Failure *RubyMinitestFailure `xml:"failure"` + File string `xml:"file,attr"` + Lineno int `xml:"lineno,attr"` + Name string `xml:"name,attr"` + Skipped *RubyMinitestSkipped `xml:"skipped"` + Time float64 `xml:"time,attr"` + + XMLName xml.Name `xml:"testcase"` +} + +type RubyMinitestTestSuite struct { + Assertions int `xml:"assertions,attr"` + Errors int `xml:"errors,attr"` + Failures int `xml:"failures,attr"` + Filepath string `xml:"filepath,attr"` + Name string `xml:"name,attr"` + Skipped int `xml:"skipped,attr"` + TestCases []RubyMinitestTestCase `xml:"testcase"` + Tests *int `xml:"tests,attr"` + Time float64 `xml:"time,attr"` + Timestamp string `xml:"timestamp,attr"` + + XMLName xml.Name `xml:"testsuite"` +} + +type RubyMinitestTestResults struct { + TestSuites []RubyMinitestTestSuite `xml:"testsuite"` + XMLName xml.Name `xml:"testsuites"` +} + +var rubyMinitestFailureLocationRegexp = regexp.MustCompile(`\n.+\(.+\)\s\[(.+)\]:\n`) + +func (p RubyMinitestParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testResults RubyMinitestTestResults + + if err := xml.NewDecoder(data).Decode(&testResults); err != nil { + return nil, errors.NewInputError("Unable to parse test results as XML: %s", err) + } + + tests := make([]v1.Test, 0) + for _, testSuite := range testResults.TestSuites { + for _, testCase := range testSuite.TestCases { + duration := time.Duration(math.Round(testCase.Time * float64(time.Second))) + lineage := []string{testCase.ClassName, testCase.Name} + name := fmt.Sprintf("%s#%s", testCase.ClassName, testCase.Name) + + var status v1.TestStatus + switch { + case testCase.Failure != nil: + status = p.NewFailedTestStatus(*testCase.Failure) + case testCase.Error != nil: + status = p.NewFailedTestStatus(*testCase.Error) + case testCase.Skipped != nil: + status = v1.NewSkippedTestStatus(nil) + default: + status = v1.NewSuccessfulTestStatus() + } + + line := testCase.Lineno + location := v1.Location{File: testCase.File, Line: &line} + + tests = append( + tests, + v1.Test{ + Name: name, + Lineage: lineage, + Location: &location, + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: map[string]any{"assertions": testCase.Assertions}, + Status: status, + }, + }, + ) + } + } + + return v1.NewTestResults( + v1.RubyMinitestFramework, + tests, + nil, + ), nil +} + +var ( + rubyMinitestNewlineRegexp = regexp.MustCompile(`\r?\n`) + rubyMinitestBacktraceRegexp = regexp.MustCompile("\\s{4}.+:in `.+'") +) + +func (p RubyMinitestParser) NewFailedTestStatus(failure RubyMinitestFailure) v1.TestStatus { + failureMessage := failure.Message + failureException := failure.Type + + if failure.Contents == nil { + return v1.NewFailedTestStatus(failureMessage, failureException, nil) + } + + lines := rubyMinitestNewlineRegexp.Split(strings.TrimSpace(*failure.Contents), -1)[2:] + + var failureBacktrace []string + + if len(lines) > 0 { + failureMessageComponents := make([]string, 0) + + for _, line := range lines { + if rubyMinitestBacktraceRegexp.Match([]byte(line)) { + failureBacktrace = append(failureBacktrace, strings.TrimSpace(line)) + } else { + failureMessageComponents = append(failureMessageComponents, line) + } + } + + constructedMessage := strings.Join(failureMessageComponents, "\n") + failureMessage = &constructedMessage + } + + if failureBacktrace == nil { + location := rubyMinitestFailureLocationRegexp.FindStringSubmatch(*failure.Contents) + if len(location) >= 2 { + failureBacktrace = []string{location[1]} + } + } + + return v1.NewFailedTestStatus(failureMessage, failureException, failureBacktrace) +} diff --git a/internal/captain/parsing/ruby_minitest_parser_test.go b/internal/captain/parsing/ruby_minitest_parser_test.go new file mode 100644 index 0000000..2318be5 --- /dev/null +++ b/internal/captain/parsing/ruby_minitest_parser_test.go @@ -0,0 +1,53 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("RubyMinitestParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/minitest.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyMinitestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses empty files", func() { + testResults, err := parsing.RubyMinitestParser{}.Parse(strings.NewReader(``)) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults.Tests).To(HaveLen(0)) + }) + + It("errors on malformed XML", func() { + testResults, err := parsing.RubyMinitestParser{}.Parse(strings.NewReader(``)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as XML")) + Expect(testResults).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/ruby_rspec_parser.go b/internal/captain/parsing/ruby_rspec_parser.go new file mode 100644 index 0000000..1b62d4a --- /dev/null +++ b/internal/captain/parsing/ruby_rspec_parser.go @@ -0,0 +1,162 @@ +package parsing + +import ( + "encoding/json" + "io" + "math" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type RubyRSpecParser struct{} + +type RubyRspecScreenshot struct { + HTML string `json:"html"` + Image string `json:"image"` +} + +type RubyRSpecException struct { + Class string `json:"class"` + Message string `json:"message"` + Backtrace []string `json:"backtrace"` +} + +type RubyRSpecExample struct { + ID *string `json:"id"` + Description string `json:"description"` + FullDescription string `json:"full_description"` + Status string `json:"status"` + FilePath string `json:"file_path"` + LineNumber int `json:"line_number"` + RunTime float64 `json:"run_time"` + PendingMessage *string `json:"pending_message"` + Exception *RubyRSpecException `json:"exception"` + Screenshot *RubyRspecScreenshot `json:"screenshot"` +} + +type RubyRSpecSummary struct { + Duration float64 `json:"duration"` + ExampleCount int `json:"example_count"` + FailureCount int `json:"failure_count"` + PendingCount int `json:"pending_count"` + ErrorsOutsideOfExamplesCount int `json:"errors_outside_of_examples_count"` +} + +type RubyRSpecTestResults struct { + Version *string `json:"version"` + Messages []string `json:"messages"` + Examples []RubyRSpecExample `json:"examples"` + Summary *RubyRSpecSummary `json:"summary"` + SummaryLine *string `json:"summary_line"` +} + +var fileRegexp = regexp.MustCompile(`\.rb(:.+|\[.+\])$`) + +func (p RubyRSpecParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testResults RubyRSpecTestResults + + if err := json.NewDecoder(data).Decode(&testResults); err != nil { + return nil, errors.NewInputError("Unable to parse test results as JSON: %s", err) + } + if testResults.Examples == nil { + return nil, errors.NewInputError("No examples were found in the JSON") + } + if testResults.Summary == nil { + return nil, errors.NewInputError("No summary was found in the JSON") + } + + tests := make([]v1.Test, 0) + for _, example := range testResults.Examples { + id := example.ID + name := example.FullDescription + + lineage := []string{ + strings.TrimSpace(strings.TrimSuffix(example.FullDescription, example.Description)), + example.Description, + } + + file := example.FilePath + if example.ID != nil { + file = fileRegexp.ReplaceAllString(*example.ID, ".rb") + } + location := v1.Location{File: file} + + duration := time.Duration(math.Round(example.RunTime * float64(time.Second))) + meta := make(map[string]any) + + if example.FilePath != "" { + meta["filePath"] = example.FilePath + } + if example.LineNumber != 0 { + meta["lineNumber"] = example.LineNumber + } + + if example.Screenshot != nil { + screenshot := make(map[string]string) + if example.Screenshot.HTML != "" { + path, err := filepath.Abs(example.Screenshot.HTML) + if err != nil { + screenshot["html"] = path + } + } + if example.Screenshot.Image != "" { + path, err := filepath.Abs(example.Screenshot.Image) + if err != nil { + screenshot["image"] = path + } + } + meta["screenshot"] = screenshot + } + + var status v1.TestStatus + switch example.Status { + case "failed": + example := example + status = v1.NewFailedTestStatus( + &example.Exception.Message, + &example.Exception.Class, + example.Exception.Backtrace, + ) + case "passed": + status = v1.NewSuccessfulTestStatus() + case "pending": + status = v1.NewPendedTestStatus(example.PendingMessage) + default: + return nil, errors.NewInputError("Unexpected status %q for example %v", example.Status, example) + } + + attempt := v1.TestAttempt{Duration: &duration, Meta: meta, Status: status} + tests = append( + tests, + v1.Test{ + ID: id, + Name: name, + Lineage: lineage, + Location: &location, + Attempt: attempt, + }, + ) + } + + otherErrors := make([]v1.OtherError, 0) + if testResults.Summary.ErrorsOutsideOfExamplesCount > 0 { + for i, message := range testResults.Messages { + if i >= testResults.Summary.ErrorsOutsideOfExamplesCount { + break + } + + otherErrors = append(otherErrors, v1.OtherError{Message: message}) + } + } + + return v1.NewTestResults( + v1.RubyRSpecFramework, + tests, + otherErrors, + ), nil +} diff --git a/internal/captain/parsing/ruby_rspec_parser_test.go b/internal/captain/parsing/ruby_rspec_parser_test.go new file mode 100644 index 0000000..be9468c --- /dev/null +++ b/internal/captain/parsing/ruby_rspec_parser_test.go @@ -0,0 +1,217 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + "time" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("RubyRSpecParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/rspec.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyRSpecParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("parses empty files", func() { + testResults, err := parsing.RubyRSpecParser{}.Parse(strings.NewReader(`{"examples": [], "summary": {}}`)) + Expect(err).ToNot(HaveOccurred()) + Expect(testResults.Tests).To(HaveLen(0)) + }) + + It("errors on malformed JSON", func() { + testResults, err := parsing.RubyRSpecParser{}.Parse(strings.NewReader(`{"examples":[`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as JSON")) + Expect(testResults).To(BeNil()) + }) + + It("errors JSON that doesn't look like RSpec", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.RubyRSpecParser{}.Parse(strings.NewReader(`{}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No examples were found in the JSON")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.RubyRSpecParser{}.Parse(strings.NewReader(`{"examples": []}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No summary was found in the JSON")) + Expect(testResults).To(BeNil()) + }) + + It("parses examples that failed", func() { + id := "./spec/some/id_spec.rb:[1:2:3]" + rspecResults := parsing.RubyRSpecTestResults{ + Examples: []parsing.RubyRSpecExample{ + { + ID: &id, + Description: "the test description", + FullDescription: "Some::Class the test description", + Status: "failed", + FilePath: "./spec/some/other/file/path_spec.rb", + LineNumber: 12, + RunTime: 0.00005, + PendingMessage: nil, + Exception: &parsing.RubyRSpecException{ + Class: "ExceptionClass", + Message: "ExceptionMessage", + Backtrace: []string{"1", "2", "3"}, + }, + }, + }, + Summary: &parsing.RubyRSpecSummary{}, + } + data, err := json.Marshal(rspecResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.RubyRSpecParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + duration := time.Duration(50000) + Expect(testResults.Tests[0]).To(Equal( + v1.Test{ + ID: &id, + Name: "Some::Class the test description", + Lineage: []string{"Some::Class", "the test description"}, + Location: &v1.Location{File: "./spec/some/id_spec.rb"}, + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: map[string]any{"filePath": "./spec/some/other/file/path_spec.rb", "lineNumber": 12}, + Status: v1.NewFailedTestStatus( + &rspecResults.Examples[0].Exception.Message, + &rspecResults.Examples[0].Exception.Class, + []string{"1", "2", "3"}, + ), + }, + }, + )) + }) + + It("parses successful examples", func() { + rspecResults := parsing.RubyRSpecTestResults{ + Examples: []parsing.RubyRSpecExample{ + { + Description: "the test description", + FullDescription: "Some::Class the test description", + Status: "passed", + FilePath: "./spec/some/other/file/path_spec.rb", + LineNumber: 12, + RunTime: 60.0, + PendingMessage: nil, + }, + }, + Summary: &parsing.RubyRSpecSummary{}, + } + data, err := json.Marshal(rspecResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.RubyRSpecParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + duration := time.Duration(60000000000) + Expect(testResults.Tests[0]).To(Equal( + v1.Test{ + Name: "Some::Class the test description", + Lineage: []string{"Some::Class", "the test description"}, + Location: &v1.Location{File: "./spec/some/other/file/path_spec.rb"}, + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: map[string]any{"filePath": "./spec/some/other/file/path_spec.rb", "lineNumber": 12}, + Status: v1.NewSuccessfulTestStatus(), + }, + }, + )) + }) + + It("parses pending examples", func() { + id := "./spec/some/id_spec.rb:[1:2:3]" + pendingMessage := "pending message" + rspecResults := parsing.RubyRSpecTestResults{ + Examples: []parsing.RubyRSpecExample{ + { + ID: &id, + Description: "the test description", + FullDescription: "Some::Class the test description", + Status: "pending", + FilePath: "./spec/some/other/file/path_spec.rb", + LineNumber: 12, + RunTime: 60.0, + PendingMessage: &pendingMessage, + }, + }, + Summary: &parsing.RubyRSpecSummary{}, + } + data, err := json.Marshal(rspecResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.RubyRSpecParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + + duration := time.Duration(60000000000) + Expect(testResults.Tests[0]).To(Equal( + v1.Test{ + ID: &id, + Name: "Some::Class the test description", + Lineage: []string{"Some::Class", "the test description"}, + Location: &v1.Location{File: "./spec/some/id_spec.rb"}, + Attempt: v1.TestAttempt{ + Duration: &duration, + Meta: map[string]any{"filePath": "./spec/some/other/file/path_spec.rb", "lineNumber": 12}, + Status: v1.NewPendedTestStatus(&pendingMessage), + }, + }, + )) + }) + + It("fails to parse other statuses", func() { + id := "./spec/some/id_spec.rb:[1:2:3]" + rspecResults := parsing.RubyRSpecTestResults{ + Examples: []parsing.RubyRSpecExample{ + { + ID: &id, + Description: "the test description", + FullDescription: "Some::Class the test description", + Status: "wat", + FilePath: "./spec/some/other/file/path_spec.rb", + LineNumber: 12, + RunTime: 60.0, + PendingMessage: nil, + }, + }, + Summary: &parsing.RubyRSpecSummary{}, + } + data, err := json.Marshal(rspecResults) + Expect(err).NotTo(HaveOccurred()) + + testResults, err := parsing.RubyRSpecParser{}.Parse(strings.NewReader(string(data))) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unexpected status \"wat\"")) + Expect(testResults).To(BeNil()) + }) + }) +}) diff --git a/internal/captain/parsing/rwx_parser.go b/internal/captain/parsing/rwx_parser.go new file mode 100644 index 0000000..725bca5 --- /dev/null +++ b/internal/captain/parsing/rwx_parser.go @@ -0,0 +1,21 @@ +package parsing + +import ( + "encoding/json" + "io" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type RWXParser struct{} + +func (p RWXParser) Parse(data io.Reader) (*v1.TestResults, error) { + var testResults v1.TestResults + + if err := json.NewDecoder(data).Decode(&testResults); err != nil { + return nil, errors.NewInputError("Unable to parse test results as JSON: %s", err) + } + + return &testResults, nil +} diff --git a/internal/captain/parsing/rwx_parser_test.go b/internal/captain/parsing/rwx_parser_test.go new file mode 100644 index 0000000..b096d50 --- /dev/null +++ b/internal/captain/parsing/rwx_parser_test.go @@ -0,0 +1,58 @@ +package parsing_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("RWXParser", func() { + Describe("Parse", func() { + It("parses the sample file", func() { + fixture, err := os.Open("../../test/fixtures/rwx/v1.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RWXParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + rwxJSON, err := json.MarshalIndent(testResults, "", " ") + Expect(err).ToNot(HaveOccurred()) + cupaloy.SnapshotT(GinkgoT(), rwxJSON) + }) + + It("errors on malformed JSON", func() { + testResults, err := parsing.RWXParser{}.Parse(strings.NewReader(`{"summary":`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as JSON")) + Expect(testResults).To(BeNil()) + }) + + It("errors on JSON that doesn't look like RWX", func() { + var testResults *v1.TestResults + var err error + + testResults, err = parsing.RWXParser{}.Parse(strings.NewReader(`{}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as JSON")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.RWXParser{}.Parse(strings.NewReader(`{"tests": []}`)) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to parse test results as JSON")) + Expect(testResults).To(BeNil()) + + testResults, err = parsing.RWXParser{}.Parse(strings.NewReader( + `{"$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json"}`, + )) + Expect(err).NotTo(HaveOccurred()) + Expect(testResults).NotTo(BeNil()) + }) + }) +}) diff --git a/internal/captain/providers/buildkite_provider.go b/internal/captain/providers/buildkite_provider.go new file mode 100644 index 0000000..f0d6e59 --- /dev/null +++ b/internal/captain/providers/buildkite_provider.go @@ -0,0 +1,146 @@ +package providers + +import ( + "strconv" + + "github.com/rwx-cloud/cli/internal/captain/config" + "github.com/rwx-cloud/cli/internal/captain/errors" +) + +type BuildkiteEnv struct { + Detected bool `env:"BUILDKITE"` + + // AttemptedBy + BuildCreatorEmail string `env:"BUILDKITE_BUILD_CREATOR_EMAIL"` + // branch + Branch string `env:"BUILDKITE_BRANCH"` + // commit message + Message string `env:"BUILDKITE_MESSAGE"` + // commit sha + Commit string `env:"BUILDKITE_COMMIT"` + // tags + BuildID string `env:"BUILDKITE_BUILD_ID"` + BuildURL string `env:"BUILDKITE_BUILD_URL"` + JobID string `env:"BUILDKITE_JOB_ID"` + Label string `env:"BUILDKITE_LABEL"` + OrganizationSlug string `env:"BUILDKITE_ORGANIZATION_SLUG"` + ParallelJob string `env:"BUILDKITE_PARALLEL_JOB"` + ParallelJobCount string `env:"BUILDKITE_PARALLEL_JOB_COUNT"` + Repo string `env:"BUILDKITE_REPO"` + RetryCount string `env:"BUILDKITE_RETRY_COUNT"` +} + +func (cfg BuildkiteEnv) makeProvider() (Provider, error) { + tags, validationError := buildkiteTags(cfg) + + if validationError != nil { + return Provider{}, validationError + } + + index, err := strconv.Atoi(cfg.ParallelJob) + if err != nil { + index = -1 + } + + total, err := strconv.Atoi(cfg.ParallelJobCount) + if err != nil { + total = -1 + } + + provider := Provider{ + AttemptedBy: cfg.BuildCreatorEmail, + BranchName: cfg.Branch, + CommitMessage: cfg.Message, + CommitSha: cfg.Commit, + JobTags: tags, + ProviderName: "buildkite", + PartitionNodes: config.PartitionNodes{ + Index: index, + Total: total, + }, + } + + return provider, nil +} + +func buildkiteTags(cfg BuildkiteEnv) (map[string]any, error) { + err := func() error { + if cfg.OrganizationSlug == "" { + return errors.NewConfigurationError( + "Missing organization slug", + "It appears that you are running on Buildkite, however Captain is unable to determine your organization slug.", + "You can configure Buildkite's organization slug by setting the BUILDKITE_ORGANIZATION_SLUG environment variable.", + ) + } + + if cfg.Repo == "" { + return errors.NewConfigurationError( + "Missing repository", + "It appears that you are running on Buildkite, however Captain is unable to determine your repository name.", + "You can configure Buildkite's repository name by setting the BUILDKITE_REPO environment variable.", + ) + } + + if cfg.Label == "" { + return errors.NewConfigurationError( + "Missing label", + "It appears that you are running on Buildkite, however Captain is unable to read Buildkite's labels.", + "You can configure labels by setting the BUILDKITE_LABEL environment variable.", + ) + } + + if cfg.JobID == "" { + return errors.NewConfigurationError( + "Missing job ID", + "It appears that you are running on Buildkite, however Captain is unable to determine Buildkite's job ID.", + "You can configure the job ID by setting the BUILDKITE_JOB_ID environment variable.", + ) + } + + if cfg.RetryCount == "" { + return errors.NewConfigurationError( + "Missing retry count", + "It appears that you are running on Buildkite, however Captain is unable to determine Buildkite's retry count.", + "You can configure the retry count by setting the BUILDKITE_RETRY_COUNT environment variable.", + ) + } + + if cfg.BuildID == "" { + return errors.NewConfigurationError( + "Missing build ID", + "It appears that you are running on Buildkite, however Captain is unable to determine Buildkite's build ID.", + "You can configure the build ID by setting the BUILDKITE_BUILD_ID environment variable.", + ) + } + + if cfg.BuildURL == "" { + return errors.NewConfigurationError( + "Missing build URL", + "It appears that you are running on Buildkite, however Captain is unable to determine Buildkite's build URL.", + "You can configure the build URL by setting the BUILDKITE_BUILD_URL environment variable.", + ) + } + + return nil + }() + + tags := map[string]any{ + "buildkite_build_id": cfg.BuildID, + "buildkite_build_url": cfg.BuildURL, + "buildkite_retry_count": cfg.RetryCount, + "buildkite_job_id": cfg.JobID, + "buildkite_label": cfg.Label, + "buildkite_repo": cfg.Repo, + "buildkite_organization_slug": cfg.OrganizationSlug, + } + + if cfg.ParallelJob != "" { + tags["buildkite_parallel_job"] = cfg.ParallelJob + } + + if cfg.ParallelJobCount != "" { + tags["buildkite_parallel_job_count"] = cfg.ParallelJobCount + } + + return tags, err +} diff --git a/internal/captain/providers/buildkite_provider_test.go b/internal/captain/providers/buildkite_provider_test.go new file mode 100644 index 0000000..3dbc49d --- /dev/null +++ b/internal/captain/providers/buildkite_provider_test.go @@ -0,0 +1,173 @@ +package providers_test + +import ( + "github.com/rwx-cloud/cli/internal/captain/config" + "github.com/rwx-cloud/cli/internal/captain/providers" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("BuildkiteEnv.makeProvider", func() { + var env providers.Env + + BeforeEach(func() { + env = providers.Env{ + Buildkite: providers.BuildkiteEnv{ + Detected: true, + Branch: "main", + BuildCreatorEmail: "foo@bar.com", + BuildID: "1234", + RetryCount: "0", + BuildURL: "https://buildkit.com/builds/42", + Message: "fixed it\nyeah", + Commit: "abc123", + JobID: "build123", + Label: "lint", + ParallelJob: "0", + ParallelJobCount: "2", + OrganizationSlug: "rwx", + Repo: "git@github.com/rwx-research/captain-cli", + }, + } + }) + + It("is valid", func() { + provider, err := env.MakeProvider() + Expect(err).To(BeNil()) + Expect(provider.AttemptedBy).To(Equal("foo@bar.com")) + Expect(provider.BranchName).To(Equal("main")) + Expect(provider.CommitSha).To(Equal("abc123")) + Expect(provider.CommitMessage).To(Equal("fixed it\nyeah")) + Expect(provider.ProviderName).To(Equal("buildkite")) + Expect(provider.Title).To(Equal("fixed it")) + Expect(provider.PartitionNodes).To(Equal(config.PartitionNodes{Index: 0, Total: 2})) + }) + + It("requires build id", func() { + env.Buildkite.BuildID = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing build ID")) + }) + + It("requires build retry count", func() { + env.Buildkite.RetryCount = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing retry count")) + }) + + It("requires build url", func() { + env.Buildkite.BuildURL = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing build URL")) + }) + + It("requires job id", func() { + env.Buildkite.JobID = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing job ID")) + }) + + It("requires job name", func() { + env.Buildkite.Label = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing label")) + }) + + It("requires buildkite org slug", func() { + env.Buildkite.OrganizationSlug = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing organization slug")) + }) + + It("requires repository url", func() { + env.Buildkite.Repo = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing repository")) + }) + + It("doesn't require node index", func() { + env.Buildkite.ParallelJob = "" + provider, err := env.MakeProvider() + Expect(err).ToNot(HaveOccurred()) + Expect(provider.PartitionNodes.Index).To(Equal(-1)) + }) + + It("doesn't require node total", func() { + env.Buildkite.ParallelJobCount = "" + provider, err := env.MakeProvider() + Expect(err).ToNot(HaveOccurred()) + Expect(provider.PartitionNodes.Total).To(Equal(-1)) + }) +}) + +var _ = Describe("BuildkiteProvider.JobTags", func() { + It("constructs job tags with parallel attributes", func() { + provider, _ := providers.Env{ + Buildkite: providers.BuildkiteEnv{ + Detected: true, + Branch: "main", + BuildCreatorEmail: "foo@bar.com", + BuildID: "1234", + RetryCount: "0", + BuildURL: "https://buildkit.com/builds/42", + Message: "fixed it", + Commit: "abc123", + JobID: "build123", + Label: "lint", + ParallelJob: "0", + ParallelJobCount: "2", + OrganizationSlug: "rwx", + Repo: "git@github.com/rwx-research/captain-cli", + }, + }.MakeProvider() + Expect(provider.JobTags).To(Equal(map[string]any{ + "buildkite_retry_count": "0", + "buildkite_repo": "git@github.com/rwx-research/captain-cli", + "buildkite_job_id": "build123", + "buildkite_label": "lint", + "buildkite_parallel_job": "0", + "buildkite_parallel_job_count": "2", + "buildkite_organization_slug": "rwx", + "buildkite_build_id": "1234", + "buildkite_build_url": "https://buildkit.com/builds/42", + })) + }) + + It("constructs job tags without parallel attributes", func() { + provider, _ := providers.Env{ + Buildkite: providers.BuildkiteEnv{ + Detected: true, + Branch: "main", + BuildCreatorEmail: "foo@bar.com", + BuildID: "1234", + RetryCount: "0", + BuildURL: "https://buildkit.com/builds/42", + Message: "fixed it", + Commit: "abc123", + JobID: "build123", + Label: "lint", + ParallelJob: "", + ParallelJobCount: "", + OrganizationSlug: "rwx", + Repo: "git@github.com/rwx-research/captain-cli", + }, + }.MakeProvider() + Expect(provider.JobTags).To(Equal(map[string]any{ + "buildkite_retry_count": "0", + "buildkite_repo": "git@github.com/rwx-research/captain-cli", + "buildkite_job_id": "build123", + "buildkite_label": "lint", + "buildkite_organization_slug": "rwx", + "buildkite_build_id": "1234", + "buildkite_build_url": "https://buildkit.com/builds/42", + })) + }) +}) diff --git a/internal/captain/providers/circleci_provider.go b/internal/captain/providers/circleci_provider.go new file mode 100644 index 0000000..4fc7067 --- /dev/null +++ b/internal/captain/providers/circleci_provider.go @@ -0,0 +1,141 @@ +package providers + +import ( + "fmt" + "strconv" + + "github.com/rwx-cloud/cli/internal/captain/config" + "github.com/rwx-cloud/cli/internal/captain/errors" +) + +type CircleCIEnv struct { + Detected bool `env:"CIRCLECI"` + + // AttemptedBy + Username string `env:"CIRCLE_USERNAME"` + // branch + Branch string `env:"CIRCLE_BRANCH"` + // commit sha + Sha1 string `env:"CIRCLE_SHA1"` + // note: no commit message + // tags + BuildNum string `env:"CIRCLE_BUILD_NUM"` + BuildURL string `env:"CIRCLE_BUILD_URL"` + Job string `env:"CIRCLE_JOB"` + NodeIndex string `env:"CIRCLE_NODE_INDEX"` + NodeTotal string `env:"CIRCLE_NODE_TOTAL"` + ProjectReponame string `env:"CIRCLE_PROJECT_REPONAME"` + ProjectUsername string `env:"CIRCLE_PROJECT_USERNAME"` + RepositoryURL string `env:"CIRCLE_REPOSITORY_URL"` +} + +func (cfg CircleCIEnv) makeProvider() (Provider, error) { + tags, validationError := circleciTags(cfg) + + if validationError != nil { + return Provider{}, validationError + } + + title := fmt.Sprintf("%s (%s)", cfg.Job, cfg.BuildNum) + + index, err := strconv.Atoi(cfg.NodeIndex) + if err != nil { + index = -1 + } + + total, err := strconv.Atoi(cfg.NodeTotal) + if err != nil { + total = -1 + } + + provider := Provider{ + AttemptedBy: cfg.Username, + BranchName: cfg.Branch, + CommitMessage: "", + CommitSha: cfg.Sha1, + JobTags: tags, + ProviderName: "circleci", + Title: title, + PartitionNodes: config.PartitionNodes{ + Index: index, + Total: total, + }, + } + + return provider, nil +} + +func circleciTags(cfg CircleCIEnv) (map[string]any, error) { + err := func() error { + // don't validate + // ParallelJobIndex -- only present if parallelization enabled + // ParallelJobTotal -- only present if parallelization enabled + + if cfg.BuildNum == "" { + return errors.NewConfigurationError( + "Missing build number", + "It appears that you are running on CircleCI, however Captain is unable to determine your build number.", + "You can configure CircleCI's build number by setting the CIRCLE_BUILD_NUM environment variable.", + ) + } + + if cfg.BuildURL == "" { + return errors.NewConfigurationError( + "Missing build URL", + "It appears that you are running on CircleCI, however Captain is unable to determine your build URL.", + "You can configure CircleCI's build URL by setting the CIRCLE_BUILD_URL environment variable.", + ) + } + + if cfg.Job == "" { + return errors.NewConfigurationError( + "Missing job name", + "It appears that you are running on CircleCI, however Captain is unable to determine your job name.", + "You can configure CircleCI's job name by setting the CIRCLE_JOB environment variable.", + ) + } + + if cfg.ProjectUsername == "" { + return errors.NewConfigurationError( + "Missing project username", + "It appears that you are running on CircleCI, however Captain is unable to determine your user-name.", + "You can configure CircleCI's user-name by setting the CIRCLE_PROJECT_USERNAME environment variable.", + ) + } + + if cfg.ProjectReponame == "" { + return errors.NewConfigurationError( + "Missing repository name", + "It appears that you are running on CircleCI, however Captain is unable to determine your repository name.", + "You can configure the repository name by setting the CIRCLE_PROJECT_REPONAME environment variable.", + ) + } + + if cfg.RepositoryURL == "" { + return errors.NewConfigurationError( + "Missing repository URL", + "It appears that you are running on CircleCI, however Captain is unable to determine your repository URL.", + "You can configure the repository URL by setting the CIRCLE_REPOSITORY_URL environment variable.", + ) + } + + return nil + }() + // these get sent to captain as-is + // name them to match the ENV vars from circle ci + // so the names in captain have parallel meaning in the circle docs + // (that's also the convention in other providers) + tags := map[string]any{ + "circle_build_num": cfg.BuildNum, + "circle_build_url": cfg.BuildURL, + "circle_job": cfg.Job, + "circle_repository_url": cfg.RepositoryURL, + "circle_project_username": cfg.ProjectUsername, + "circle_project_reponame": cfg.ProjectReponame, + } + if cfg.NodeIndex != "" && cfg.NodeTotal != "" { + tags["circle_node_index"] = cfg.NodeIndex + tags["circle_node_total"] = cfg.NodeTotal + } + return tags, err +} diff --git a/internal/captain/providers/circleci_provider_test.go b/internal/captain/providers/circleci_provider_test.go new file mode 100644 index 0000000..65cc143 --- /dev/null +++ b/internal/captain/providers/circleci_provider_test.go @@ -0,0 +1,173 @@ +package providers_test + +import ( + "github.com/rwx-cloud/cli/internal/captain/config" + "github.com/rwx-cloud/cli/internal/captain/providers" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("CircleCiEnv.makeProvider", func() { + var env providers.Env + + BeforeEach(func() { + env = providers.Env{CircleCI: providers.CircleCIEnv{ + Detected: true, + BuildNum: "18", + BuildURL: "https://circleci.com/gh/rwx-research/captain-cli/18", + Job: "build", + + NodeIndex: "0", + NodeTotal: "2", + + ProjectUsername: "rwx", + ProjectReponame: "captain-cli", + RepositoryURL: "git@github.com:rwx-research/captain-cli.git", + + Branch: "main", + Sha1: "000bd5713d35f778fb51d2b0bf034e8fff5b60b1", + Username: "test", + }} + }) + + It("is valid", func() { + provider, err := env.MakeProvider() + Expect(err).To(BeNil()) + Expect(provider.AttemptedBy).To(Equal("test")) + Expect(provider.BranchName).To(Equal("main")) + Expect(provider.CommitSha).To(Equal("000bd5713d35f778fb51d2b0bf034e8fff5b60b1")) + Expect(provider.CommitMessage).To(Equal("")) + Expect(provider.ProviderName).To(Equal("circleci")) + Expect(provider.Title).To(Equal("build (18)")) + Expect(provider.PartitionNodes).To(Equal(config.PartitionNodes{Index: 0, Total: 2})) + }) + + It("requires BuildNum", func() { + env.CircleCI.BuildNum = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing build number")) + }) + + It("requires BuildURL", func() { + env.CircleCI.BuildURL = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing build URL")) + }) + + It("requires JobName", func() { + env.CircleCI.Job = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing job name")) + }) + + It("does not require NodeIndex", func() { + env.CircleCI.NodeIndex = "" + _, err := env.MakeProvider() + Expect(err).ToNot(HaveOccurred()) + }) + + It("does not require NodeTotal", func() { + env.CircleCI.NodeTotal = "" + _, err := env.MakeProvider() + Expect(err).ToNot(HaveOccurred()) + }) + + It("requires RepoAccountName", func() { + env.CircleCI.ProjectUsername = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing project username")) + }) + + It("requires RepoName", func() { + env.CircleCI.ProjectReponame = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing repository name")) + }) + + It("requires RepoURL", func() { + env.CircleCI.RepositoryURL = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing repository URL")) + }) + + It("doesn't require node index", func() { + env.CircleCI.NodeIndex = "" + provider, err := env.MakeProvider() + Expect(err).ToNot(HaveOccurred()) + Expect(provider.PartitionNodes.Index).To(Equal(-1)) + }) + + It("doesn't require node total", func() { + env.CircleCI.NodeTotal = "" + provider, err := env.MakeProvider() + Expect(err).ToNot(HaveOccurred()) + Expect(provider.PartitionNodes.Total).To(Equal(-1)) + }) +}) + +var _ = Describe("Circleciparams.JobTags", func() { + It("constructs job tags with parallel attributes", func() { + provider, _ := providers.Env{CircleCI: providers.CircleCIEnv{ + Detected: true, + BuildNum: "18", + BuildURL: "https://circleci.com/gh/rwx-research/captain-cli/18", + Job: "build", + NodeIndex: "0", + NodeTotal: "2", + + ProjectUsername: "rwx", + ProjectReponame: "captain-cli", + RepositoryURL: "git@github.com:rwx-research/captain-cli.git", + + Branch: "main", + Sha1: "000bd5713d35f778fb51d2b0bf034e8fff5b60b1", + Username: "test", + }}.MakeProvider() + + Expect(provider.JobTags).To(Equal(map[string]any{ + "circle_build_num": "18", + "circle_build_url": "https://circleci.com/gh/rwx-research/captain-cli/18", + "circle_job": "build", + "circle_repository_url": "git@github.com:rwx-research/captain-cli.git", + "circle_project_username": "rwx", + "circle_project_reponame": "captain-cli", + "circle_node_index": "0", + "circle_node_total": "2", + })) + }) + + It("constructs job tags without parallel attributes", func() { + provider, _ := providers.Env{CircleCI: providers.CircleCIEnv{ + Detected: true, + BuildNum: "18", + BuildURL: "https://circleci.com/gh/rwx-research/captain-cli/18", + Job: "build", + NodeIndex: "", + NodeTotal: "", + + ProjectUsername: "rwx", + ProjectReponame: "captain-cli", + RepositoryURL: "git@github.com:rwx-research/captain-cli.git", + + Branch: "main", + Sha1: "000bd5713d35f778fb51d2b0bf034e8fff5b60b1", + Username: "test", + }}.MakeProvider() + + Expect(provider.JobTags).To(Equal(map[string]any{ + "circle_build_num": "18", + "circle_build_url": "https://circleci.com/gh/rwx-research/captain-cli/18", + "circle_job": "build", + "circle_repository_url": "git@github.com:rwx-research/captain-cli.git", + "circle_project_username": "rwx", + "circle_project_reponame": "captain-cli", + })) + }) +}) diff --git a/internal/captain/providers/generic_provider.go b/internal/captain/providers/generic_provider.go new file mode 100644 index 0000000..5b990a4 --- /dev/null +++ b/internal/captain/providers/generic_provider.go @@ -0,0 +1,65 @@ +package providers + +import "github.com/rwx-cloud/cli/internal/captain/config" + +// TODO: add node_index, node_total +// - make it available to the run command +// - make it accessible with to the generic provider +// - expose it via job_tags + +type GenericEnv struct { + Who string + Branch string + Sha string + CommitMessage string + BuildURL string + Title string + PartitionIndex int `env:"RWX_TEST_PARTITION_INDEX" envDefault:"-1"` + PartitionTotal int `env:"RWX_TEST_PARTITION_TOTAL" envDefault:"-1"` +} + +func (cfg GenericEnv) MakeProvider() Provider { + return Provider{ + AttemptedBy: cfg.Who, + BranchName: cfg.Branch, + CommitSha: cfg.Sha, + CommitMessage: cfg.CommitMessage, + ProviderName: "generic", + JobTags: map[string]any{"captain_build_url": cfg.BuildURL}, + Title: cfg.Title, + PartitionNodes: config.PartitionNodes{ + Index: cfg.PartitionIndex, + Total: cfg.PartitionTotal, + }, + } +} + +// GitMetadataProvider is the subset of git functionality needed to populate generic env defaults. +type GitMetadataProvider interface { + GetUser() string + GetBranch() string + GetHeadSha() string +} + +// PopulateFromGit fills empty fields from git as lowest-priority defaults. +func (cfg *GenericEnv) PopulateFromGit(gitClient GitMetadataProvider) { + if cfg.Who == "" { + cfg.Who = gitClient.GetUser() + } + if cfg.Branch == "" { + cfg.Branch = gitClient.GetBranch() + } + if cfg.Sha == "" { + cfg.Sha = gitClient.GetHeadSha() + } +} + +func MergeGeneric(into GenericEnv, from GenericEnv) GenericEnv { + into.Who = firstNonempty(from.Who, into.Who) + into.Branch = firstNonempty(from.Branch, into.Branch) + into.Sha = firstNonempty(from.Sha, into.Sha) + into.CommitMessage = firstNonempty(from.CommitMessage, into.CommitMessage) + into.BuildURL = firstNonempty(from.BuildURL, into.BuildURL) + into.Title = firstNonempty(from.Title, into.Title) + return into +} diff --git a/internal/captain/providers/generic_provider_test.go b/internal/captain/providers/generic_provider_test.go new file mode 100644 index 0000000..ab57848 --- /dev/null +++ b/internal/captain/providers/generic_provider_test.go @@ -0,0 +1,67 @@ +package providers_test + +import ( + "github.com/rwx-cloud/cli/internal/captain/providers" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GenericEnv.MakeProvider", func() { + It("constructs job tags", func() { + provider := providers.GenericEnv{ + Who: "test", + Branch: "testing-env-vars", + Sha: "abc123", + CommitMessage: "Testing env vars -- the commit message", + BuildURL: "https://jenkins.example.com/job/test/123/", + Title: "Some title", + }.MakeProvider() + + Expect(provider.JobTags).To(Equal(map[string]any{"captain_build_url": "https://jenkins.example.com/job/test/123/"})) + }) +}) + +var _ = Describe("MergeGeneric", func() { + It("merges Who", func() { + Expect(providers.MergeGeneric(providers.GenericEnv{Who: "p1"}, + providers.GenericEnv{Who: "p2"}).Who).To(Equal("p2")) + Expect(providers.MergeGeneric(providers.GenericEnv{Who: "p1"}, + providers.GenericEnv{}).Who).To(Equal("p1")) + }) + + It("merges Branch", func() { + Expect(providers.MergeGeneric(providers.GenericEnv{Branch: "p1"}, + providers.GenericEnv{Branch: "p2"}).Branch).To(Equal("p2")) + Expect(providers.MergeGeneric(providers.GenericEnv{Branch: "p1"}, + providers.GenericEnv{}).Branch).To(Equal("p1")) + }) + + It("merges Sha", func() { + Expect(providers.MergeGeneric(providers.GenericEnv{Sha: "p1"}, + providers.GenericEnv{Sha: "p2"}).Sha).To(Equal("p2")) + Expect(providers.MergeGeneric(providers.GenericEnv{Sha: "p1"}, + providers.GenericEnv{}).Sha).To(Equal("p1")) + }) + + It("merges CommitMessage", func() { + Expect(providers.MergeGeneric(providers.GenericEnv{CommitMessage: "p1"}, + providers.GenericEnv{CommitMessage: "p2"}).CommitMessage).To(Equal("p2")) + Expect(providers.MergeGeneric(providers.GenericEnv{CommitMessage: "p1"}, + providers.GenericEnv{}).CommitMessage).To(Equal("p1")) + }) + + It("merges BuildURL", func() { + Expect(providers.MergeGeneric(providers.GenericEnv{BuildURL: "p1"}, + providers.GenericEnv{BuildURL: "p2"}).BuildURL).To(Equal("p2")) + Expect(providers.MergeGeneric(providers.GenericEnv{BuildURL: "p1"}, + providers.GenericEnv{}).BuildURL).To(Equal("p1")) + }) + + It("merges Title", func() { + Expect(providers.MergeGeneric(providers.GenericEnv{Title: "p1"}, + providers.GenericEnv{Title: "p2"}).Title).To(Equal("p2")) + Expect(providers.MergeGeneric(providers.GenericEnv{Title: "p1"}, + providers.GenericEnv{}).Title).To(Equal("p1")) + }) +}) diff --git a/internal/captain/providers/github_provider.go b/internal/captain/providers/github_provider.go new file mode 100644 index 0000000..42700c5 --- /dev/null +++ b/internal/captain/providers/github_provider.go @@ -0,0 +1,165 @@ +package providers + +import ( + "encoding/json" + "fmt" + "os" + "path" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" +) + +type GitHubEnv struct { + Detected bool `env:"GITHUB_ACTIONS"` + + // attempted by + ExecutingActor string `env:"GITHUB_ACTOR"` + TriggeringActor string `env:"GITHUB_TRIGGERING_ACTOR"` + + // branch + EventName string `env:"GITHUB_EVENT_NAME"` + RefName string `env:"GITHUB_REF_NAME"` + HeadRef string `env:"GITHUB_HEAD_REF"` + + // commit message is parsed from the event payload + EventPath string `env:"GITHUB_EVENT_PATH"` + + // commit sha + CommitSha string `env:"GITHUB_SHA"` + + // workflow + Workflow string `env:"GITHUB_WORKFLOW"` + + // tags + Attempt string `env:"GITHUB_RUN_ATTEMPT"` + ID string `env:"GITHUB_RUN_ID"` + Name string `env:"GITHUB_JOB"` + Repository string `env:"GITHUB_REPOSITORY"` +} + +type GitHubEventPayloadData struct { + HeadCommit struct { + Message string `json:"message"` + } `json:"head_commit"` + PullRequest struct { + Number int `json:"number"` + Title string `json:"title"` + } `json:"pull_request"` + Issue struct { + Number int `json:"number"` + Title string `json:"title"` + } `json:"issue"` +} + +func (cfg GitHubEnv) makeProvider() (Provider, error) { + eventPayloadData := GitHubEventPayloadData{} + + file, err := os.Open(cfg.EventPath) + if err != nil && !os.IsNotExist(err) { + return Provider{}, errors.Wrap(err, "unable to open event payload file") + } else if err == nil { + if err := json.NewDecoder(file).Decode(&eventPayloadData); err != nil { + return Provider{}, errors.Wrap(err, "failed to decode event payload data") + } + } + + return cfg.MakeProviderWithoutCommitMessageParsing(eventPayloadData) +} + +// this function is only here to make the provider easier to test without writing the event file to disk +func (cfg GitHubEnv) MakeProviderWithoutCommitMessageParsing(payloadData GitHubEventPayloadData) (Provider, error) { + branchName := cfg.RefName + if cfg.EventName == "pull_request" { + branchName = cfg.HeadRef + } + + attemptedBy := cfg.TriggeringActor + if attemptedBy == "" { + attemptedBy = cfg.ExecutingActor + } + + tags, validationError := githubTags(cfg, payloadData) + if validationError != nil { + return Provider{}, validationError + } + + if payloadData.PullRequest.Title == "" && payloadData.Issue.Title != "" { + payloadData.PullRequest.Title = payloadData.Issue.Title + payloadData.PullRequest.Number = payloadData.Issue.Number + } + + commitMessage := payloadData.HeadCommit.Message + var title string + if commitMessage == "" { + if payloadData.PullRequest.Title == "" { + title = cfg.Workflow + } else { + title = fmt.Sprintf("%s (PR #%d)", payloadData.PullRequest.Title, payloadData.PullRequest.Number) + } + } + + provider := Provider{ + AttemptedBy: attemptedBy, + BranchName: branchName, + CommitMessage: commitMessage, + CommitSha: cfg.CommitSha, + JobTags: tags, + ProviderName: "github", + Title: title, + } + + return provider, nil +} + +func githubTags(cfg GitHubEnv, eventPayloadData GitHubEventPayloadData) (map[string]any, error) { + err := func() error { + if cfg.ID == "" { + return errors.NewConfigurationError( + "Missing run ID", + "It appears that you are running on Github Actions, however Captain is unable to determine your run ID.", + "You can configure Github's run ID by setting the GITHUB_RUN_ID environment variable.", + ) + } + + if cfg.Attempt == "" { + return errors.NewConfigurationError( + "Missing run attempt", + "It appears that you are running on Github Actions, however Captain is unable to determine your run attempt "+ + "number.", + "You can configure Github's run attempt number by setting the GITHUB_RUN_ATTEMPT environment variable.", + ) + } + + if cfg.Name == "" { + return errors.NewConfigurationError( + "Missing job name", + "It appears that you are running on Github Actions, however Captain is unable to determine your job name.", + "You can configure Github's job name by setting the GITHUB_JOB environment variable.", + ) + } + + if cfg.Repository == "" { + return errors.NewConfigurationError( + "Missing repository", + "It appears that you are running on Github Actions, however Captain is unable to determine your repository name.", + "You can configure the repository name by setting the GITHUB_REPOSITORY environment variable.", + ) + } + + return nil + }() + owner, repo := path.Split(cfg.Repository) + + tags := map[string]any{ + "github_run_id": cfg.ID, + "github_run_attempt": cfg.Attempt, + "github_repository_name": repo, + "github_account_owner": strings.TrimSuffix(owner, "/"), + "github_job_name": cfg.Name, + "github_pr_title": eventPayloadData.PullRequest.Title, + "github_pr_number": eventPayloadData.PullRequest.Number, + } + + return tags, err +} diff --git a/internal/captain/providers/github_provider_test.go b/internal/captain/providers/github_provider_test.go new file mode 100644 index 0000000..c09f16b --- /dev/null +++ b/internal/captain/providers/github_provider_test.go @@ -0,0 +1,164 @@ +package providers_test + +import ( + "github.com/rwx-cloud/cli/internal/captain/providers" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GitHubEnv.makeProvider", func() { + // TODO: it'd be nice to have a fixture file with a real event payload. For now we test _most_ of the github provider + // with the MakeProviderWithoutCommitMessageParsing func which only depends on the GitHubEnv struct. + var params providers.GitHubEnv + var eventPayloadData providers.GitHubEventPayloadData + + BeforeEach(func() { + params = providers.GitHubEnv{ + // for branch name + EventName: "push", + RefName: "main", + HeadRef: "main", + // for commit message + EventPath: "/tmp/event.json", + // attempted by + ExecutingActor: "test", + TriggeringActor: "test", + // for commit sha + CommitSha: "abc123", + // for workflow title + Workflow: "workflow.yml", + // for tags + ID: "123", + Attempt: "1", + Repository: "rwx/captain-cli", + Name: "some-job", + } + eventPayloadData = providers.GitHubEventPayloadData{} + eventPayloadData.HeadCommit.Message = "fixed it\nyeah" + eventPayloadData.PullRequest.Number = 5 + eventPayloadData.PullRequest.Title = "PR title" + }) + + It("is valid", func() { + provider, err := params.MakeProviderWithoutCommitMessageParsing(eventPayloadData) + Expect(err).To(BeNil()) + Expect(provider.AttemptedBy).To(Equal("test")) + Expect(provider.BranchName).To(Equal("main")) + Expect(provider.CommitSha).To(Equal("abc123")) + Expect(provider.CommitMessage).To(Equal("fixed it\nyeah")) + Expect(provider.ProviderName).To(Equal("github")) + }) + + It("uses the PR info for the title when the commit message is missing", func() { + eventPayloadData.HeadCommit.Message = "" + provider, err := params.MakeProviderWithoutCommitMessageParsing(eventPayloadData) + Expect(err).To(BeNil()) + Expect(provider.AttemptedBy).To(Equal("test")) + Expect(provider.BranchName).To(Equal("main")) + Expect(provider.CommitSha).To(Equal("abc123")) + Expect(provider.CommitMessage).To(Equal("")) + Expect(provider.ProviderName).To(Equal("github")) + Expect(provider.Title).To(Equal("PR title (PR #5)")) + }) + + It("uses the issue info for the title when the commit message and PR title are missing", func() { + eventPayloadData.HeadCommit.Message = "" + eventPayloadData.Issue.Title = "Issue title" + eventPayloadData.Issue.Number = 3 + eventPayloadData.PullRequest.Title = "" + provider, err := params.MakeProviderWithoutCommitMessageParsing(eventPayloadData) + Expect(err).To(BeNil()) + Expect(provider.AttemptedBy).To(Equal("test")) + Expect(provider.BranchName).To(Equal("main")) + Expect(provider.CommitSha).To(Equal("abc123")) + Expect(provider.CommitMessage).To(Equal("")) + Expect(provider.ProviderName).To(Equal("github")) + Expect(provider.Title).To(Equal("Issue title (PR #3)")) + }) + + It("uses the github workflow name for the title when the commit message, and titles are missing", func() { + eventPayloadData.HeadCommit.Message = "" + eventPayloadData.Issue.Title = "" + eventPayloadData.PullRequest.Title = "" + provider, err := params.MakeProviderWithoutCommitMessageParsing(eventPayloadData) + Expect(err).To(BeNil()) + Expect(provider.AttemptedBy).To(Equal("test")) + Expect(provider.BranchName).To(Equal("main")) + Expect(provider.CommitSha).To(Equal("abc123")) + Expect(provider.CommitMessage).To(Equal("")) + Expect(provider.ProviderName).To(Equal("github")) + Expect(provider.Title).To(Equal("workflow.yml")) + }) + + It("requires an repository", func() { + params.Repository = "" + + _, err := params.MakeProviderWithoutCommitMessageParsing(eventPayloadData) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing repository")) + }) + + It("requires an job name", func() { + params.Name = "" + _, err := params.MakeProviderWithoutCommitMessageParsing(eventPayloadData) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing job name")) + }) + + It("requires a run attempt name", func() { + params.Attempt = "" + _, err := params.MakeProviderWithoutCommitMessageParsing(eventPayloadData) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing run attempt")) + }) + + It("requires a run ID", func() { + params.ID = "" + _, err := params.MakeProviderWithoutCommitMessageParsing(eventPayloadData) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing run ID")) + }) +}) + +var _ = Describe("GithubProvider.JobTags", func() { + It("constructs job tags", func() { + eventPayloadData := providers.GitHubEventPayloadData{} + eventPayloadData.HeadCommit.Message = "fixed it\nyeah" + eventPayloadData.PullRequest.Number = 5 + eventPayloadData.PullRequest.Title = "PR title" + + provider, err := providers.GitHubEnv{ + // for branch name + EventName: "push", + RefName: "main", + HeadRef: "main", + // for commit message + EventPath: "/tmp/event.json", + // attempted by + ExecutingActor: "test", + TriggeringActor: "test", + // for commit sha + CommitSha: "abc123", + + // for tags + ID: "my_run_id", + Attempt: "my_run_attempt", + Repository: "my_account_name/my_repo_name", + Name: "my_job_name", + }.MakeProviderWithoutCommitMessageParsing(eventPayloadData) + + Expect(err).To(BeNil()) + + Expect(provider.JobTags).To(Equal(map[string]any{ + "github_run_id": "my_run_id", + "github_run_attempt": "my_run_attempt", + "github_repository_name": "my_repo_name", + "github_account_owner": "my_account_name", + "github_job_name": "my_job_name", + "github_pr_number": 5, + "github_pr_title": "PR title", + })) + }) +}) diff --git a/internal/captain/providers/gitlab_ci_provider.go b/internal/captain/providers/gitlab_ci_provider.go new file mode 100644 index 0000000..95a94d1 --- /dev/null +++ b/internal/captain/providers/gitlab_ci_provider.go @@ -0,0 +1,185 @@ +package providers + +import ( + "strconv" + + "github.com/rwx-cloud/cli/internal/captain/config" + "github.com/rwx-cloud/cli/internal/captain/errors" +) + +type GitLabEnv struct { + // see https://docs.gitlab.com/ee/ci/variables/predefined_variables.html + // gitlab/runner version all/all + Detected bool `env:"GITLAB_CI"` + + // Build Info + JobName string `env:"CI_JOB_NAME"` // gitlab/runner version 9.0/0.5 + JobStage string `env:"CI_JOB_STAGE"` // gitlab/runner version 9.0/0.5 + JobID string `env:"CI_JOB_ID"` // gitlab/runner version 9.0/all + PipelineID string `env:"CI_PIPELINE_ID"` // gitlab/runner version 8.10/all + JobURL string `env:"CI_JOB_URL"` // gitlab/runner version 11.1/0.5 + PipelineURL string `env:"CI_PIPELINE_URL"` // gitlab/runner version 11.1/0.5 + UserLogin string `env:"GITLAB_USER_LOGIN"` // gitlab/runner version 10.0/all + NodeTotal string `env:"CI_NODE_TOTAL"` // gitlab/runner version 11.5/all + NodeIndex string `env:"CI_NODE_INDEX"` // gitlab/runner version 11.5/all + + // Repo Info + ProjectPath string `env:"CI_PROJECT_PATH"` // gitlab/runner version 8.10/0.5 + ProjectURL string `env:"CI_PROJECT_URL"` // gitlab/runner version 8.10/0.5 + + // Commit Info + CommitSHA string `env:"CI_COMMIT_SHA"` // gitlab/runner version 9.0/all + CommitAuthor string `env:"CI_COMMIT_AUTHOR"` // gitlab/runner version 13.11/all + CommitBranch string `env:"CI_COMMIT_BRANCH"` // gitlab/runner version 12.6/0.5 + CommitMessage string `env:"CI_COMMIT_MESSAGE"` // gitlab/runner version 10.8/all + + // Consider in the future checking CI_SERVER_VERSION >= 13.2 (the newest version with all of these fields) + // gitlab/runner version 11.7/all + APIV4URL string `env:"CI_API_V4_URL"` +} + +func (cfg GitLabEnv) makeProvider() (Provider, error) { + attemptedBy := cfg.UserLogin + if attemptedBy == "" { + // presumably if there's no attempted by, the build was triggered by pushing the commit / the commit author + attemptedBy = cfg.CommitAuthor + } + + tags, validationError := gitlabciTags(cfg) + if validationError != nil { + return Provider{}, validationError + } + + index, err := strconv.Atoi(cfg.NodeIndex) + if err != nil { + index = 0 + } + + total, err := strconv.Atoi(cfg.NodeTotal) + if err != nil { + total = -1 + } + + provider := Provider{ + AttemptedBy: attemptedBy, + BranchName: cfg.CommitBranch, + CommitMessage: cfg.CommitMessage, + CommitSha: cfg.CommitSHA, + JobTags: tags, + ProviderName: "gitlabci", + PartitionNodes: config.PartitionNodes{Index: index - 1, Total: total}, // gitlab indexes from 1 + } + + return provider, nil +} + +func gitlabciTags(cfg GitLabEnv) (map[string]any, error) { + err := func() error { + if cfg.JobName == "" { + return errors.NewConfigurationError( + "Missing job name", + "It appears that you are running on a GitLab runner, however Captain is unable to determine your job name.", + "You can configure GitLab's job name by setting the CI_JOB_NAME environment variable.", + ) + } + + if cfg.JobStage == "" { + return errors.NewConfigurationError( + "Missing job stage", + "It appears that you are running on a GitLab runner, however Captain is unable to determine your job stage.", + "You can configure GitLab's job stage by setting the CI_JOB_STAGE environment variable.", + ) + } + + if cfg.JobID == "" { + return errors.NewConfigurationError( + "Missing job ID", + "It appears that you are running on a GitLab runner, however Captain is unable to determine your job ID.", + "You can configure GitLab's job ID by setting the CI_JOB_ID environment variable.", + ) + } + + if cfg.PipelineID == "" { + return errors.NewConfigurationError( + "Missing pipeline ID", + "It appears that you are running on a GitLab runner, however Captain is unable to determine your pipeline ID.", + "You can configure GitLab's pipeline ID by setting the CI_PIPELINE_ID environment variable.", + ) + } + + if cfg.JobURL == "" { + return errors.NewConfigurationError( + "Missing job URL", + "It appears that you are running on a GitLab runner, however Captain is unable to determine your job URL.", + "You can configure GitLab's job URL by setting the CI_JOB_URL environment variable.", + ) + } + + if cfg.PipelineURL == "" { + return errors.NewConfigurationError( + "Missing pipeline URL", + "It appears that you are running on a GitLab runner, however Captain is unable to determine your pipeline URL.", + "You can configure GitLab's pipeline URL by setting the CI_PIPELINE_URL environment variable.", + ) + } + + if cfg.NodeTotal == "" { + return errors.NewConfigurationError( + "Missing node total", + "It appears that you are running on a GitLab runner, however Captain is unable to determine your total node count.", + "You can configure GitLab's node count by setting the CI_NODE_TOTAL environment variable.", + ) + } + + if cfg.ProjectPath == "" { + return errors.NewConfigurationError( + "Missing project path", + "It appears that you are running on a GitLab runner, however Captain is unable to determine your project path.", + "You can configure GitLab's project path by setting the CI_PROJECT_PATH environment variable.", + ) + } + + if cfg.ProjectURL == "" { + return errors.NewConfigurationError( + "Missing project URL", + "It appears that you are running on a GitLab runner, however Captain is unable to determine your project URL.", + "You can configure GitLab's project URL by setting the CI_PROJECT_URL environment variable.", + ) + } + + if cfg.APIV4URL == "" { + return errors.NewConfigurationError( + "Missing API URL", + "It appears that you are running on a GitLab runner, however Captain is unable to determine GitLab's API endpoint.", + "You can configure the API endpoint by setting the CI_API_V4_URL environment variable.", + ) + } + + return nil + }() + if err != nil { + return nil, err + } + // these get sent to captain as-is + // name them to match the ENV vars from GitLab ci + // so the names in captain have parallel meaning in the GitLab docs + // (that's also the convention in other providers) + tags := map[string]any{ + "gitlab_job_name": cfg.JobName, + "gitlab_job_stage": cfg.JobStage, + "gitlab_job_id": cfg.JobID, + "gitlab_pipeline_id": cfg.PipelineID, + "gitlab_job_url": cfg.JobURL, + "gitlab_pipeline_url": cfg.PipelineURL, + "gitlab_repository_path": cfg.ProjectPath, + "gitlab_project_url": cfg.ProjectURL, + "gitlab_api_v4_url": cfg.APIV4URL, + } + + if cfg.NodeIndex != "" && cfg.NodeTotal != "" { + tags["gitlab_node_index"] = cfg.NodeIndex + tags["gitlab_node_total"] = cfg.NodeTotal + } + + return tags, nil +} diff --git a/internal/captain/providers/gitlab_ci_provider_test.go b/internal/captain/providers/gitlab_ci_provider_test.go new file mode 100644 index 0000000..0c86df7 --- /dev/null +++ b/internal/captain/providers/gitlab_ci_provider_test.go @@ -0,0 +1,211 @@ +package providers_test + +import ( + "github.com/rwx-cloud/cli/internal/captain/config" + "github.com/rwx-cloud/cli/internal/captain/providers" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GitLabEnv.MakeProvider", func() { + var env providers.Env + + BeforeEach(func() { + env = providers.Env{GitLab: providers.GitLabEnv{ + Detected: true, + JobName: "rspec 1/2", + JobStage: "test", + JobID: "3889399980", + PipelineID: "798778026", + JobURL: "https://gitlab.com/captain-examples/rspec/-/jobs/3889399980", + PipelineURL: "https://gitlab.com/captain-examples/rspec/-/pipelines/798778026", + UserLogin: "michaelglass", + NodeTotal: "2", + NodeIndex: "1", + ProjectPath: "captain-examples/rspec", + ProjectURL: "https://gitlab.com/captain-examples/rspec", + CommitSHA: "03d68a49ef1e131cf8942d5a07a0ff008ede6a1a", + CommitAuthor: "Michael Glass ", + CommitBranch: "main", + CommitMessage: "this is what I did\nand here are some details", + APIV4URL: "https://gitlab.com/api/v4", + }} + }) + + It("is valid", func() { + provider, err := env.MakeProvider() + Expect(err).To(BeNil()) + Expect(provider.AttemptedBy).To(Equal("michaelglass")) + Expect(provider.BranchName).To(Equal("main")) + Expect(provider.CommitSha).To(Equal("03d68a49ef1e131cf8942d5a07a0ff008ede6a1a")) + Expect(provider.CommitMessage).To(Equal("this is what I did\nand here are some details")) + Expect(provider.ProviderName).To(Equal("gitlabci")) + Expect(provider.Title).To(Equal("this is what I did")) + Expect(provider.PartitionNodes).To(Equal(config.PartitionNodes{Index: 0, Total: 2})) + }) + + It("falls back to commit author if attempted by is not set", func() { + env.GitLab.UserLogin = "" + provider, _ := env.MakeProvider() + + Expect(provider.AttemptedBy).To(Equal("Michael Glass ")) + }) + + It("requires JobName", func() { + env.GitLab.JobName = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing job name")) + }) + + It("requires JobStage", func() { + env.GitLab.JobStage = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing job stage")) + }) + + It("requires JobID", func() { + env.GitLab.JobID = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing job ID")) + }) + + It("requires PipelineID", func() { + env.GitLab.PipelineID = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing pipeline ID")) + }) + + It("requires JobURL", func() { + env.GitLab.JobURL = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing job URL")) + }) + + It("requires PipelineURL", func() { + env.GitLab.PipelineURL = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing pipeline URL")) + }) + + It("doesn't requires AttemptedBy", func() { + env.GitLab.UserLogin = "" + _, err := env.MakeProvider() + + Expect(err).ToNot(HaveOccurred()) + }) + + It("doesn't requires NodeIndex", func() { + env.GitLab.NodeIndex = "" + provider, err := env.MakeProvider() + Expect(err).ToNot(HaveOccurred()) + Expect(provider.PartitionNodes.Index).To(Equal(-1)) + }) + + It("requires NodeTotal", func() { + env.GitLab.NodeTotal = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing node total")) + }) + + It("requires project path", func() { + env.GitLab.ProjectPath = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing project path")) + }) + + It("requires ProjectURL", func() { + env.GitLab.ProjectURL = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing project URL")) + }) + + It("requires API URL", func() { + env.GitLab.APIV4URL = "" + + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing API URL")) + }) +}) + +var _ = Describe("GitLabCIenv.GitLab.JobTags", func() { + It("constructs job tags with parallel attributes", func() { + provider, _ := providers.Env{GitLab: providers.GitLabEnv{ + Detected: true, + JobName: "rspec 1/2", + JobStage: "test", + JobID: "3889399980", + PipelineID: "798778026", + JobURL: "https://gitlab.com/captain-examples/rspec/-/jobs/3889399980", + PipelineURL: "https://gitlab.com/captain-examples/rspec/-/pipelines/798778026", + UserLogin: "michaelglass", + NodeTotal: "2", + NodeIndex: "1", + ProjectPath: "captain-examples/rspec", + ProjectURL: "https://gitlab.com/captain-examples/rspec", + CommitSHA: "03d68a49ef1e131cf8942d5a07a0ff008ede6a1a", + CommitAuthor: "Michael Glass ", + CommitBranch: "main", + CommitMessage: "this is what I did\nand here are some details", + APIV4URL: "https://gitlab.com/api/v4", + }}.MakeProvider() + + Expect(provider.JobTags).To(Equal(map[string]any{ + "gitlab_project_url": "https://gitlab.com/captain-examples/rspec", + "gitlab_job_name": "rspec 1/2", + "gitlab_job_stage": "test", + "gitlab_job_id": "3889399980", + "gitlab_pipeline_id": "798778026", + "gitlab_job_url": "https://gitlab.com/captain-examples/rspec/-/jobs/3889399980", + "gitlab_pipeline_url": "https://gitlab.com/captain-examples/rspec/-/pipelines/798778026", + "gitlab_repository_path": "captain-examples/rspec", + "gitlab_api_v4_url": "https://gitlab.com/api/v4", + "gitlab_node_index": "1", + "gitlab_node_total": "2", + })) + }) + + It("constructs job tags without parallel attributes", func() { + provider, _ := providers.Env{GitLab: providers.GitLabEnv{ + Detected: true, + JobName: "rspec", + JobStage: "test", + JobID: "3889399980", + PipelineID: "798778026", + JobURL: "https://gitlab.com/captain-examples/rspec/-/jobs/3889399980", + PipelineURL: "https://gitlab.com/captain-examples/rspec/-/pipelines/798778026", + UserLogin: "michaelglass", + NodeTotal: "1", + NodeIndex: "", + ProjectPath: "captain-examples/rspec", + ProjectURL: "https://gitlab.com/captain-examples/rspec", + CommitSHA: "03d68a49ef1e131cf8942d5a07a0ff008ede6a1a", + CommitAuthor: "Michael Glass ", + CommitBranch: "main", + CommitMessage: "this is what I did\nand here are some details", + APIV4URL: "https://gitlab.com/api/v4", + }}.MakeProvider() + + Expect(provider.JobTags).To(Equal(map[string]any{ + "gitlab_job_stage": "test", + "gitlab_job_id": "3889399980", + "gitlab_pipeline_id": "798778026", + "gitlab_job_url": "https://gitlab.com/captain-examples/rspec/-/jobs/3889399980", + "gitlab_pipeline_url": "https://gitlab.com/captain-examples/rspec/-/pipelines/798778026", + "gitlab_repository_path": "captain-examples/rspec", + "gitlab_project_url": "https://gitlab.com/captain-examples/rspec", + "gitlab_job_name": "rspec", + "gitlab_api_v4_url": "https://gitlab.com/api/v4", + })) + }) +}) diff --git a/internal/captain/providers/mint_provider.go b/internal/captain/providers/mint_provider.go new file mode 100644 index 0000000..45f0188 --- /dev/null +++ b/internal/captain/providers/mint_provider.go @@ -0,0 +1,250 @@ +package providers + +import ( + "fmt" + "strconv" + + "github.com/rwx-cloud/cli/internal/captain/config" + "github.com/rwx-cloud/cli/internal/captain/errors" +) + +type MintEnv struct { + Detected bool `env:"RWX"` + + ParallelIndex *string `env:"RWX_PARALLEL_INDEX"` + ParallelTotal *string `env:"RWX_PARALLEL_TOTAL"` + ParallelGroupCacheKey *string `env:"RWX_PARALLEL_GROUP_CACHE_KEY"` + Actor string `env:"RWX_ACTOR"` + ActorID *string `env:"RWX_ACTOR_ID"` + RunURL string `env:"RWX_RUN_URL"` + TaskURL string `env:"RWX_TASK_URL"` + RunID string `env:"RWX_RUN_ID"` + TaskID string `env:"RWX_TASK_ID"` + TaskAttemptNumber string `env:"RWX_TASK_ATTEMPT_NUMBER"` + RunTitle string `env:"RWX_RUN_TITLE"` + GitRepositoryURL string `env:"RWX_GIT_REPOSITORY_URL"` + GitRepositoryName string `env:"RWX_GIT_REPOSITORY_NAME"` + GitCommitMessage string `env:"RWX_GIT_COMMIT_MESSAGE"` + GitCommitSha string `env:"RWX_GIT_COMMIT_SHA"` + GitRef string `env:"RWX_GIT_REF"` + GitRefName string `env:"RWX_GIT_REF_NAME"` +} + +func (cfg MintEnv) makeProvider() (Provider, error) { + tags, validationError := mintTags(cfg) + + if validationError != nil { + return Provider{}, validationError + } + + index := -1 + if cfg.ParallelIndex != nil { + var err error + index, err = strconv.Atoi(*cfg.ParallelIndex) + if err != nil { + index = -1 + } + } + + total := -1 + if cfg.ParallelTotal != nil { + var err error + total, err = strconv.Atoi(*cfg.ParallelTotal) + if err != nil { + total = -1 + } + } + + timingManifestKey := "" + if cfg.ParallelGroupCacheKey != nil { + timingManifestKey = fmt.Sprintf("rwx-cache-key-%s", *cfg.ParallelGroupCacheKey) + } + + provider := Provider{ + AttemptedBy: cfg.Actor, + BranchName: cfg.GitRefName, + CommitMessage: cfg.GitCommitMessage, + CommitSha: cfg.GitCommitSha, + JobTags: tags, + ProviderName: "mint", + TimingManifestKey: timingManifestKey, + Title: cfg.RunTitle, + PartitionNodes: config.PartitionNodes{ + Index: index, + Total: total, + }, + } + + return provider, nil +} + +func mintTags(cfg MintEnv) (map[string]any, error) { + err := func() error { + // don't validate + // ActorID -- expected to be sometimes unset + // ParallelIndex -- only present if parallelization enabled + // ParallelTotal -- only present if parallelization enabled + // ParallelGroupCacheKey -- only present if parallelization enabled + + if cfg.Actor == "" { + return errors.NewConfigurationError( + "Missing actor", + "It appears that you are running on RWX (`RWX` is set to `true`),"+ + " however Captain is unable to determine your actor.", + "Make sure you are running on RWX. If not, set `RWX` to `false`.", + ) + } + + if cfg.RunURL == "" { + return errors.NewConfigurationError( + "Missing run url", + "It appears that you are running on RWX (`RWX` is set to `true`),"+ + " however Captain is unable to determine your run url.", + "Make sure you are running on RWX. If not, set `RWX` to `false`.", + ) + } + + if cfg.TaskURL == "" { + return errors.NewConfigurationError( + "Missing task url", + "It appears that you are running on RWX (`RWX` is set to `true`),"+ + " however Captain is unable to determine your task url.", + "Make sure you are running on RWX. If not, set `RWX` to `false`.", + ) + } + + if cfg.RunID == "" { + return errors.NewConfigurationError( + "Missing run id", + "It appears that you are running on RWX (`RWX` is set to `true`),"+ + " however Captain is unable to determine your run id.", + "Make sure you are running on RWX. If not, set `RWX` to `false`.", + ) + } + + if cfg.TaskID == "" { + return errors.NewConfigurationError( + "Missing task id", + "It appears that you are running on RWX (`RWX` is set to `true`),"+ + " however Captain is unable to determine your task id.", + "Make sure you are running on RWX. If not, set `RWX` to `false`.", + ) + } + + if cfg.TaskAttemptNumber == "" { + return errors.NewConfigurationError( + "Missing task attempt number", + "It appears that you are running on RWX (`RWX` is set to `true`),"+ + " however Captain is unable to determine your task attempt number.", + "Make sure you are running on RWX. If not, set `RWX` to `false`.", + ) + } + + if cfg.RunTitle == "" { + return errors.NewConfigurationError( + "Missing run title", + "It appears that you are running on RWX (`RWX` is set to `true`),"+ + " however Captain is unable to determine your run title.", + "Make sure you are running on RWX. If not, set `RWX` to `false`.", + ) + } + + if cfg.GitRepositoryURL == "" { + return errors.NewConfigurationError( + "Missing git repository url", + "It appears that you are running on RWX (`RWX` is set to `true`),"+ + " however Captain is unable to determine your git repository url.", + "Ensure that you've run the `git/clone` package to set the `RWX_GIT_*` metadata.", + ) + } + + if cfg.GitRepositoryName == "" { + return errors.NewConfigurationError( + "Missing git repository name", + "It appears that you are running on RWX (`RWX` is set to `true`),"+ + " however Captain is unable to determine your git repository name.", + "Ensure that you've run the `git/clone` package to set the `RWX_GIT_*` metadata.", + ) + } + + if cfg.GitCommitMessage == "" { + return errors.NewConfigurationError( + "Missing git commit message", + "It appears that you are running on RWX (`RWX` is set to `true`),"+ + " however Captain is unable to determine your git commit message.", + "Ensure that you've run the `git/clone` package to set the `RWX_GIT_*` metadata.", + ) + } + + if cfg.GitCommitSha == "" { + return errors.NewConfigurationError( + "Missing git commit sha", + "It appears that you are running on RWX (`RWX` is set to `true`),"+ + " however Captain is unable to determine your git commit sha.", + "Ensure that you've run the `git/clone` package to set the `RWX_GIT_*` metadata.", + ) + } + + if cfg.GitRef == "" { + return errors.NewConfigurationError( + "Missing git ref", + "It appears that you are running on RWX (`RWX` is set to `true`),"+ + " however Captain is unable to determine your git ref.", + "Ensure that you've run the `git/clone` package to set the `MINT_GIT_*` metadata.", + ) + } + + if cfg.GitRefName == "" { + return errors.NewConfigurationError( + "Missing git ref name", + "It appears that you are running on RWX (`RWX` is set to `true`),"+ + " however Captain is unable to determine your git ref name.", + "Ensure that you've run the `git/clone` package to set the `RWX_GIT_*` metadata.", + ) + } + + return nil + }() + + tags := map[string]any{ + "mint_actor": cfg.Actor, + "mint_run_url": cfg.RunURL, + "mint_task_url": cfg.TaskURL, + "mint_run_id": cfg.RunID, + "mint_task_id": cfg.TaskID, + "mint_task_attempt_number": cfg.TaskAttemptNumber, + "mint_run_title": cfg.RunTitle, + "mint_git_repository_url": cfg.GitRepositoryURL, + "mint_git_repository_name": cfg.GitRepositoryName, + "mint_git_commit_message": cfg.GitCommitMessage, + "mint_git_commit_sha": cfg.GitCommitSha, + "mint_git_ref": cfg.GitRef, + "mint_git_ref_name": cfg.GitRefName, + } + + if cfg.ParallelIndex != nil { + tags["mint_parallel_index"] = *cfg.ParallelIndex + } else { + tags["mint_parallel_index"] = nil + } + + if cfg.ParallelTotal != nil { + tags["mint_parallel_total"] = *cfg.ParallelTotal + } else { + tags["mint_parallel_total"] = nil + } + + if cfg.ParallelGroupCacheKey != nil { + tags["rwx_parallel_group_cache_key"] = *cfg.ParallelGroupCacheKey + } else { + tags["rwx_parallel_group_cache_key"] = nil + } + + if cfg.ActorID != nil { + tags["mint_actor_id"] = *cfg.ActorID + } else { + tags["mint_actor_id"] = nil + } + + return tags, err +} diff --git a/internal/captain/providers/mint_provider_test.go b/internal/captain/providers/mint_provider_test.go new file mode 100644 index 0000000..8fa20a3 --- /dev/null +++ b/internal/captain/providers/mint_provider_test.go @@ -0,0 +1,221 @@ +package providers_test + +import ( + "github.com/rwx-cloud/cli/internal/captain/config" + "github.com/rwx-cloud/cli/internal/captain/providers" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("MintEnv.MakeProvider", func() { + var env providers.Env + + BeforeEach(func() { + partitionIndex := "0" + partitionTotal := "2" + actorID := "some-actor-id" + env = providers.Env{Mint: providers.MintEnv{ + Detected: true, + ParallelIndex: &partitionIndex, + ParallelTotal: &partitionTotal, + ActorID: &actorID, + Actor: "some-actor", + RunURL: "https://cloud.rwx.com/mint/some-org/runs/some-run-id", + TaskURL: "https://cloud.rwx.com/mint/some-org/tasks/some-task-id", + RunID: "some-run-id", + TaskID: "some-task-id", + TaskAttemptNumber: "1", + RunTitle: "Some title", + GitRepositoryURL: "git@github.com:rwx-research/captain.git", + GitRepositoryName: "rwx-research/captain", + GitCommitMessage: "Some commit message", + GitCommitSha: "some-commit-sha", + GitRef: "refs/heads/some-ref", + GitRefName: "some-ref", + }} + }) + + It("is valid", func() { + provider, err := env.MakeProvider() + Expect(err).To(BeNil()) + Expect(provider.AttemptedBy).To(Equal("some-actor")) + Expect(provider.BranchName).To(Equal("some-ref")) + Expect(provider.CommitSha).To(Equal("some-commit-sha")) + Expect(provider.CommitMessage).To(Equal("Some commit message")) + Expect(provider.ProviderName).To(Equal("mint")) + Expect(provider.Title).To(Equal("Some title")) + Expect(provider.PartitionNodes).To(Equal(config.PartitionNodes{Index: 0, Total: 2})) + }) + + It("requires Actor", func() { + env.Mint.Actor = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing actor")) + }) + It("requires RunURL", func() { + env.Mint.RunURL = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing run url")) + }) + It("requires TaskURL", func() { + env.Mint.TaskURL = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing task url")) + }) + It("requires RunID", func() { + env.Mint.RunID = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing run id")) + }) + It("requires TaskID", func() { + env.Mint.TaskID = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing task id")) + }) + It("requires TaskAttemptNumber", func() { + env.Mint.TaskAttemptNumber = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing task attempt number")) + }) + It("requires RunTitle", func() { + env.Mint.RunTitle = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing run title")) + }) + It("requires GitRepositoryURL", func() { + env.Mint.GitRepositoryURL = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing git repository url")) + }) + It("requires GitRepositoryName", func() { + env.Mint.GitRepositoryName = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing git repository name")) + }) + It("requires GitCommitMessage", func() { + env.Mint.GitCommitMessage = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing git commit message")) + }) + It("requires GitCommitSha", func() { + env.Mint.GitCommitSha = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing git commit sha")) + }) + It("requires GitRef", func() { + env.Mint.GitRef = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing git ref")) + }) + It("requires GitRefName", func() { + env.Mint.GitRefName = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing git ref name")) + }) +}) + +var _ = Describe("MintEnv.JobTags", func() { + It("constructs job tags with optional attributes", func() { + partitionIndex := "0" + partitionTotal := "2" + actorID := "some-actor-id" + parallelGroupCacheKey := "abcdef" + provider, _ := providers.Env{Mint: providers.MintEnv{ + Detected: true, + ParallelIndex: &partitionIndex, + ParallelTotal: &partitionTotal, + ParallelGroupCacheKey: ¶llelGroupCacheKey, + ActorID: &actorID, + Actor: "some-actor", + RunURL: "https://cloud.rwx.com/mint/some-org/runs/some-run-id", + TaskURL: "https://cloud.rwx.com/mint/some-org/tasks/some-task-id", + RunID: "some-run-id", + TaskID: "some-task-id", + TaskAttemptNumber: "1", + RunTitle: "Some title", + GitRepositoryURL: "git@github.com:rwx-research/captain.git", + GitRepositoryName: "rwx-research/captain", + GitCommitMessage: "Some commit message", + GitCommitSha: "some-commit-sha", + GitRef: "refs/heads/some-ref", + GitRefName: "some-ref", + }}.MakeProvider() + + Expect(provider.JobTags).To(Equal(map[string]any{ + "mint_actor": "some-actor", + "mint_run_url": "https://cloud.rwx.com/mint/some-org/runs/some-run-id", + "mint_task_url": "https://cloud.rwx.com/mint/some-org/tasks/some-task-id", + "mint_run_id": "some-run-id", + "mint_task_id": "some-task-id", + "mint_task_attempt_number": "1", + "mint_run_title": "Some title", + "mint_git_repository_url": "git@github.com:rwx-research/captain.git", + "mint_git_repository_name": "rwx-research/captain", + "mint_git_commit_message": "Some commit message", + "mint_git_commit_sha": "some-commit-sha", + "mint_git_ref": "refs/heads/some-ref", + "mint_git_ref_name": "some-ref", + "mint_parallel_index": partitionIndex, + "mint_parallel_total": partitionTotal, + "mint_actor_id": actorID, + "rwx_parallel_group_cache_key": "abcdef", + })) + }) + + It("constructs job tags without optional attributes", func() { + provider, _ := providers.Env{Mint: providers.MintEnv{ + Detected: true, + ParallelIndex: nil, + ParallelTotal: nil, + ParallelGroupCacheKey: nil, + ActorID: nil, + Actor: "some-actor", + RunURL: "https://cloud.rwx.com/mint/some-org/runs/some-run-id", + TaskURL: "https://cloud.rwx.com/mint/some-org/tasks/some-task-id", + RunID: "some-run-id", + TaskID: "some-task-id", + TaskAttemptNumber: "1", + RunTitle: "Some title", + GitRepositoryURL: "git@github.com:rwx-research/captain.git", + GitRepositoryName: "rwx-research/captain", + GitCommitMessage: "Some commit message", + GitCommitSha: "some-commit-sha", + GitRef: "refs/heads/some-ref", + GitRefName: "some-ref", + }}.MakeProvider() + + Expect(provider.JobTags).To(Equal(map[string]any{ + "mint_actor": "some-actor", + "mint_run_url": "https://cloud.rwx.com/mint/some-org/runs/some-run-id", + "mint_task_url": "https://cloud.rwx.com/mint/some-org/tasks/some-task-id", + "mint_run_id": "some-run-id", + "mint_task_id": "some-task-id", + "mint_task_attempt_number": "1", + "mint_run_title": "Some title", + "mint_git_repository_url": "git@github.com:rwx-research/captain.git", + "mint_git_repository_name": "rwx-research/captain", + "mint_git_commit_message": "Some commit message", + "mint_git_commit_sha": "some-commit-sha", + "mint_git_ref": "refs/heads/some-ref", + "mint_git_ref_name": "some-ref", + "mint_parallel_index": nil, + "mint_parallel_total": nil, + "mint_actor_id": nil, + "rwx_parallel_group_cache_key": nil, + })) + }) +}) diff --git a/internal/captain/providers/provider.go b/internal/captain/providers/provider.go new file mode 100644 index 0000000..e6387e5 --- /dev/null +++ b/internal/captain/providers/provider.go @@ -0,0 +1,147 @@ +package providers + +import ( + "strings" + + "github.com/rwx-cloud/cli/internal/captain/config" + "github.com/rwx-cloud/cli/internal/captain/errors" +) + +type Env struct { + Buildkite BuildkiteEnv + CircleCI CircleCIEnv + Generic GenericEnv + GitHub GitHubEnv + GitLab GitLabEnv + Mint MintEnv +} + +type Provider struct { + AttemptedBy string + BranchName string + CommitMessage string + CommitSha string + JobTags map[string]any + ProviderName string + TimingManifestKey string + Title string + PartitionNodes config.PartitionNodes +} + +func Validate(p Provider) error { + err := p.validate() + if err == nil { + return nil + } + + if p.ProviderName == "generic" { + return errors.NewConfigurationError( + "Unsupported runtime environment", + "rwx test was unable to detect the presence of a supported CI provider.", + "To use rwx test locally or on unsupported hosts, please use the --who, --branch, and --sha flags. "+ + "Alternatively, these options can also be set via the RWX_TEST_WHO, RWX_TEST_BRANCH, and RWX_TEST_SHA "+ + "environment variables.", + ) + } + return err +} + +// assigns default validation error if it exists +func (p Provider) validate() error { + if p.ProviderName == "" { + return errors.NewInternalError("provider name should never be empty") + } + + if p.AttemptedBy == "" { + return errors.NewConfigurationError( + "Missing 'who'", + "rwx test requires the name of the user that triggered a build / test run.", + "You can specify this name by using the --who flag. Alternatively you can also set this option "+ + "using the RWX_TEST_WHO environment variable.", + ) + } + + if p.BranchName == "" { + return errors.NewConfigurationError( + "Missing branch name", + "rwx test requires a branch name in order to track test runs correctly.", + "You can specify this name by using the --branch flag. Alternatively you can also set this option "+ + "using the RWX_TEST_BRANCH environment variable.", + ) + } + + if p.CommitSha == "" { + return errors.NewConfigurationError( + "Missing commit SHA", + "rwx test requires a commit SHA in order to track test runs correctly.", + "You can specify the SHA by using the --sha flag or the RWX_TEST_SHA environment variable", + ) + } + + return nil +} + +// merge merges attempted by, branch name, and commitsha +// preferring the values from `from` if they are not empty +// if into has an empty provider name, we assume it's not set and take all values from `from` +func Merge(into Provider, from Provider) Provider { + if into.ProviderName != "" { + into.AttemptedBy = firstNonempty(from.AttemptedBy, into.AttemptedBy) + into.BranchName = firstNonempty(from.BranchName, into.BranchName) + into.CommitSha = firstNonempty(from.CommitSha, into.CommitSha) + into.CommitMessage = firstNonempty(from.CommitMessage, into.CommitMessage) + into.Title = firstNonempty(from.Title, into.Title) + + if into.PartitionNodes.Total <= 0 && from.PartitionNodes.Total >= 1 { + into.PartitionNodes = from.PartitionNodes + } + + return into + } + return from +} + +// return the first non-empty string +func firstNonempty(strs ...string) string { + for _, str := range strs { + if str != "" { + return str + } + } + + return "" +} + +func (env Env) MakeProvider() (Provider, error) { + // detect provider from environment if we can + wrapError := func(p Provider, err error) (Provider, error) { + return p, errors.Wrap(err, "error building detected provider") + } + detectedProvider, err := func() (Provider, error) { + switch { + case env.GitHub.Detected: + return wrapError(env.GitHub.makeProvider()) + case env.Buildkite.Detected: + return wrapError(env.Buildkite.makeProvider()) + case env.CircleCI.Detected: + return wrapError(env.CircleCI.makeProvider()) + case env.GitLab.Detected: + return wrapError(env.GitLab.makeProvider()) + case env.Mint.Detected: + return wrapError(env.Mint.makeProvider()) + } + return Provider{}, nil + }() + if err != nil { + return Provider{}, err + } + + mergedWithGeneric := Merge(detectedProvider, env.Generic.MakeProvider()) + + if mergedWithGeneric.Title == "" { + mergedWithGeneric.Title = strings.Split(mergedWithGeneric.CommitMessage, "\n")[0] + } + + // merge the generic provider into the detected provider. The captain-specific flags & env vars take precedence. + return mergedWithGeneric, nil +} diff --git a/internal/captain/providers/provider_test.go b/internal/captain/providers/provider_test.go new file mode 100644 index 0000000..6bfe255 --- /dev/null +++ b/internal/captain/providers/provider_test.go @@ -0,0 +1,422 @@ +package providers_test + +import ( + "github.com/rwx-cloud/cli/internal/captain/config" + "github.com/rwx-cloud/cli/internal/captain/providers" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Validate", func() { + var provider providers.Provider + BeforeEach(func() { + provider = providers.Provider{ + AttemptedBy: "me", + BranchName: "main", + CommitSha: "abc123", + JobTags: map[string]any{}, + ProviderName: "", + Title: "some-title", + } + }) + + Context("with a generic provider", func() { + BeforeEach(func() { + provider.ProviderName = "generic" + }) + + It("has a special error message if invalid", func() { + provider.AttemptedBy = "" + err := providers.Validate(provider) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unsupported runtime environment")) + }) + }) + + Context("with a non-generic provider", func() { + BeforeEach(func() { + provider.ProviderName = "github" + }) + + It("is valid", func() { + Expect(providers.Validate(provider)).To(BeNil()) + }) + + It("is invalid if ProviderName is empty", func() { + provider.ProviderName = "" + err := providers.Validate(provider) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("provider name should never be empty")) + }) + + It("is invalid if AttemptedBy is empty", func() { + provider.AttemptedBy = "" + err := providers.Validate(provider) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing 'who'")) + }) + + It("is invalid if BranchName is empty", func() { + provider.BranchName = "" + err := providers.Validate(provider) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing branch name")) + }) + It("is invalid if CommitSha is empty", func() { + provider.CommitSha = "" + err := providers.Validate(provider) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing commit SHA")) + }) + It("is VALID if CommitMessage is empty", func() { + provider.CommitMessage = "" + err := providers.Validate(provider) + Expect(err).To(BeNil()) + }) + It("is VALID if Title is empty", func() { + provider.Title = "" + err := providers.Validate(provider) + Expect(err).To(BeNil()) + }) + }) +}) + +var _ = Describe("providers.Merge", func() { + provider1 := providers.Provider{ + AttemptedBy: "me", + BranchName: "main", + CommitSha: "abc123", + CommitMessage: "one commit message", + JobTags: map[string]any{}, + ProviderName: "generic", + Title: "some-title", + PartitionNodes: config.PartitionNodes{Index: -1, Total: -1}, + } + + provider2 := providers.Provider{ + AttemptedBy: "you", + BranchName: "test-branch", + CommitSha: "qrs789", + CommitMessage: "another commit message", + JobTags: map[string]any{}, + ProviderName: "GitHub", + Title: "another-title", + PartitionNodes: config.PartitionNodes{Index: 0, Total: 2}, + } + + It("returns the second provider if the first is empty", func() { + merged := providers.Merge(providers.Provider{}, provider1) + Expect(merged).To(Equal(provider1)) + }) + + It("changes nothing if the second provider is empty", func() { + merged := providers.Merge(provider1, providers.Provider{}) + Expect(merged).To(Equal(provider1)) + }) + + It("merges all fields except provider name", func() { + merged := providers.Merge(provider1, provider2) + Expect(merged).ToNot(Equal(provider2)) + Expect(merged.ProviderName).To(Equal(provider1.ProviderName)) + + merged.ProviderName = provider2.ProviderName + Expect(merged).To(Equal(provider2)) + }) + + It("won't merge PartitionNodes if first node has valid values", func() { + provider1.PartitionNodes = config.PartitionNodes{Index: 1, Total: 3} + merged := providers.Merge(provider1, provider2) + Expect(merged.PartitionNodes).To(Equal(provider1.PartitionNodes)) + }) + + It("merges partition nodes when into implicitly unset and from is explicitly set", func() { + genericWithNodes := providers.Provider{ + AttemptedBy: "me", + BranchName: "main", + CommitSha: "abc123", + CommitMessage: "one commit message", + JobTags: map[string]any{}, + ProviderName: "generic", + Title: "some-title", + PartitionNodes: config.PartitionNodes{Index: 0, Total: 2}, + } + + githubWithoutNodes := providers.Provider{ + AttemptedBy: "you", + BranchName: "test-branch", + CommitSha: "qrs789", + CommitMessage: "another commit message", + JobTags: map[string]any{}, + ProviderName: "GitHub", + Title: "another-title", + // NOTE: no PartitionNodes are set + } + + merged := providers.Merge(githubWithoutNodes, genericWithNodes) + Expect(merged.PartitionNodes.Index).To(Equal(0)) + Expect(merged.PartitionNodes.Total).To(Equal(2)) + }) + + It("merges partition nodes when into explicitly unset and from is explicitly set", func() { + genericWithNodes := providers.Provider{ + AttemptedBy: "me", + BranchName: "main", + CommitSha: "abc123", + CommitMessage: "one commit message", + JobTags: map[string]any{}, + ProviderName: "generic", + Title: "some-title", + PartitionNodes: config.PartitionNodes{Index: 0, Total: 2}, + } + + githubWithoutNodes := providers.Provider{ + AttemptedBy: "you", + BranchName: "test-branch", + CommitSha: "qrs789", + CommitMessage: "another commit message", + JobTags: map[string]any{}, + ProviderName: "GitHub", + Title: "another-title", + PartitionNodes: config.PartitionNodes{Index: -1, Total: -1}, + } + + merged := providers.Merge(githubWithoutNodes, genericWithNodes) + Expect(merged.PartitionNodes.Index).To(Equal(0)) + Expect(merged.PartitionNodes.Total).To(Equal(2)) + }) + + It("does not merge partition nodes when into explicitly set and from is explicitly set", func() { + genericWithNodes := providers.Provider{ + AttemptedBy: "me", + BranchName: "main", + CommitSha: "abc123", + CommitMessage: "one commit message", + JobTags: map[string]any{}, + ProviderName: "generic", + Title: "some-title", + PartitionNodes: config.PartitionNodes{Index: 0, Total: 2}, + } + + githubWithoutNodes := providers.Provider{ + AttemptedBy: "you", + BranchName: "test-branch", + CommitSha: "qrs789", + CommitMessage: "another commit message", + JobTags: map[string]any{}, + ProviderName: "GitHub", + Title: "another-title", + PartitionNodes: config.PartitionNodes{Index: 1, Total: 3}, + } + + merged := providers.Merge(githubWithoutNodes, genericWithNodes) + Expect(merged.PartitionNodes.Index).To(Equal(1)) + Expect(merged.PartitionNodes.Total).To(Equal(3)) + }) +}) + +var _ = Describe("MakeProvider", func() { + var env providers.Env + + BeforeEach(func() { + env = providers.Env{} + }) + + Context("with no detected provider info", func() { + Context("but with a valid generic provider", func() { + It("returns the generic provider", func() { + env.Generic.Who = "me" + env.Generic.Branch = "main" + env.Generic.Sha = "abc123" + + provider, err := env.MakeProvider() + Expect(err).NotTo(HaveOccurred()) + Expect(provider).To(Equal( + providers.Provider{ + ProviderName: "generic", + CommitSha: "abc123", + BranchName: "main", + AttemptedBy: "me", + CommitMessage: "", + Title: "", + JobTags: map[string]any{"captain_build_url": ""}, + }, + )) + }) + }) + + Context("but with an invalid generic provider", func() { + It("returns the invalid generic provider", func() { + // different commands have different concepts of what a valid generic provider is + // so we can't validate that in MakeProvider + env.Generic.Who = "me" + provider, err := env.MakeProvider() + Expect(err).ToNot(HaveOccurred()) + Expect(provider.AttemptedBy).To(Equal("me")) + }) + }) + + It("when title is set, uses the set title", func() { + env.Generic.Title = "foo" + provider, err := env.MakeProvider() + Expect(err).ToNot(HaveOccurred()) + Expect(provider.Title).To(Equal("foo")) + Expect(provider.CommitMessage).To(Equal("")) + }) + + Context("When commit message has a single line", func() { + BeforeEach(func() { + env.Generic.CommitMessage = "fixed it" + }) + + It("when title is unset, sets title to the commit message", func() { + provider, err := env.MakeProvider() + Expect(err).ToNot(HaveOccurred()) + Expect(provider.Title).To(Equal("fixed it")) + Expect(provider.CommitMessage).To(Equal("fixed it")) + }) + It("when title is set, uses the set title", func() { + env.Generic.Title = "didn't fix it yet" + provider, err := env.MakeProvider() + Expect(err).ToNot(HaveOccurred()) + Expect(provider.Title).To(Equal("didn't fix it yet")) + Expect(provider.CommitMessage).To(Equal("fixed it")) + }) + }) + + Context("When commit message has a multiple lines line", func() { + BeforeEach(func() { + env.Generic.CommitMessage = "fixed it\nit was tricky!" + }) + + It("when title is unset, sets title to the first line of the commit message", func() { + provider, err := env.MakeProvider() + Expect(err).ToNot(HaveOccurred()) + Expect(provider.Title).To(Equal("fixed it")) + Expect(provider.CommitMessage).To(Equal("fixed it\nit was tricky!")) + }) + It("when title is set, uses the set title", func() { + env.Generic.Title = "didn't fix it yet" + provider, err := env.MakeProvider() + Expect(err).ToNot(HaveOccurred()) + Expect(provider.Title).To(Equal("didn't fix it yet")) + Expect(provider.CommitMessage).To(Equal("fixed it\nit was tricky!")) + }) + }) + }) + + Context("with valid detected provider info", func() { + BeforeEach(func() { + env.Buildkite = providers.BuildkiteEnv{ + Detected: true, + Branch: "main", + BuildCreatorEmail: "foo@bar.com", + BuildID: "1234", + RetryCount: "0", + BuildURL: "https://buildkit.com/builds/42", + Message: "fixed it", + Commit: "abc123", + JobID: "build123", + Label: "lint", + ParallelJob: "0", + ParallelJobCount: "2", + OrganizationSlug: "rwx", + Repo: "git@github.com/rwx-research/captain-cli", + } + }) + It("returns provider info", func() { + provider, err := env.MakeProvider() + + Expect(err).NotTo(HaveOccurred()) + Expect(provider).To(Equal( + providers.Provider{ + ProviderName: "buildkite", + CommitSha: "abc123", + BranchName: "main", + AttemptedBy: "foo@bar.com", + CommitMessage: "fixed it", + Title: "fixed it", + PartitionNodes: config.PartitionNodes{Index: 0, Total: 2}, + JobTags: map[string]any{ + "buildkite_retry_count": "0", + "buildkite_parallel_job_count": "2", + "buildkite_build_id": "1234", + "buildkite_build_url": "https://buildkit.com/builds/42", + "buildkite_job_id": "build123", + "buildkite_label": "lint", + "buildkite_repo": "git@github.com/rwx-research/captain-cli", + "buildkite_organization_slug": "rwx", + "buildkite_parallel_job": "0", + }, + }, + )) + }) + + Context("and with a generic provider", func() { + It("returns the merged provider with the title from the generic provider", func() { + env.Generic.Sha = "qrs789" + env.Generic.CommitMessage = "fixed it on Tuesday\nthis commit message annotated before writing to captain" + provider, err := env.MakeProvider() + Expect(err).NotTo(HaveOccurred()) + Expect(provider).To(Equal( + providers.Provider{ + ProviderName: "buildkite", + CommitSha: "qrs789", + BranchName: "main", + AttemptedBy: "foo@bar.com", + CommitMessage: "fixed it on Tuesday\nthis commit message annotated before writing to captain", + Title: "fixed it on Tuesday", + PartitionNodes: config.PartitionNodes{Total: 2, Index: 0}, + JobTags: map[string]any{ + "buildkite_retry_count": "0", + "buildkite_parallel_job_count": "2", + "buildkite_build_id": "1234", + "buildkite_build_url": "https://buildkit.com/builds/42", + "buildkite_job_id": "build123", + "buildkite_label": "lint", + "buildkite_repo": "git@github.com/rwx-research/captain-cli", + "buildkite_organization_slug": "rwx", + "buildkite_parallel_job": "0", + }, + }, + )) + }) + }) + }) + + Context("with invalid detected provider info", func() { + BeforeEach(func() { + env.Buildkite = providers.BuildkiteEnv{ + Detected: true, + BuildCreatorEmail: "foo@bar.com", + BuildID: "1234", + RetryCount: "0", + BuildURL: "https://buildkit.com/builds/42", + Message: "fixed it", + Commit: "abc123", + JobID: "build123", + Label: "lint", + ParallelJob: "0", + ParallelJobCount: "2", + OrganizationSlug: "rwx", + Repo: "git@github.com/rwx-research/captain-cli", + } + }) + It("is invalid", func() { + provider, _ := env.MakeProvider() + err := providers.Validate(provider) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing branch name")) + }) + + Context("and with a generic provider that will be valid after merge", func() { + It("returns the merged provider", func() { + env.Generic.Branch = "main" + provider, _ := env.MakeProvider() + err := providers.Validate(provider) + Expect(err).ToNot(HaveOccurred()) + Expect(provider.BranchName).To(Equal("main")) + }) + }) + }) +}) diff --git a/internal/captain/providers/providers_suite_test.go b/internal/captain/providers/providers_suite_test.go new file mode 100644 index 0000000..be3ea08 --- /dev/null +++ b/internal/captain/providers/providers_suite_test.go @@ -0,0 +1,15 @@ +package providers_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestTesting(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "Providers Suite") +} diff --git a/internal/captain/reporting/.snapshots/Markdown Report produces a readable summary when cloud is disabled b/internal/captain/reporting/.snapshots/Markdown Report produces a readable summary when cloud is disabled new file mode 100644 index 0000000..6c19157 --- /dev/null +++ b/internal/captain/reporting/.snapshots/Markdown Report produces a readable summary when cloud is disabled @@ -0,0 +1,140 @@ +# `some-suite-id` Summary + +9 tests, 1 flaky, 3 failed, 1 timed out, 1 quarantined, 1 canceled, 1 skipped, 2 retries + +## 🔁 Flaky + +
    +flaky test + +
    +
    Retried 1 time
    +
    Defined at some/path/to/file.rb:15
    + + +
    +
    +Failure Details
    + +
    expected true to equal false
    +
    +file/path/one.rb:4
    +file/path/two.rb:4
    +file/path/three.rb:4
    + +
    +
    + +
    +
    + +## ❌ Failed + +
    +failed test + +
    +
    Retried 2 times
    +
    Defined at some/path/to/file.rb
    +
    Retry with bundle exec rspec './spec/foo/bar.rb[4:5:6]'
    + +
    +
    +Failure Details
    + +
    expected true to equal false
    +
    +file/path/one.rb:4
    +file/path/two.rb:4
    +file/path/three.rb:4
    + +
    +
    + +
    +
    +
    +failed test backtrace only + +
    + +
    Defined at some/path/to/file.rb
    +
    Retry with bundle exec rspec './spec/foo/bar.rb[7:8:9]'
    + +
    +
    +Failure Details
    + +
    file/path/one.rb:4
    +file/path/two.rb:4
    +file/path/three.rb:4
    + +
    +
    + +
    +
    +
    +failed test message only w/ ansi + +
    + + +
    Retry with bundle exec rspec './spec/foo/bar.rb:15'
    + +
    +
    +Failure Details
    + +
    Failure/Error: expect(thanos).to eq("inevitable")
    +
    +  expected: "inevitable"
    +       got: "evitable"
    +
    +  (compared using ==)
    + +
    +
    + +
    +
    + +## ⏳ Timed Out + +
    +timed out test + +
    + + +
    Retry with bundle exec rspec './spec/foo/bar.rb:12'
    + +
    +
    + +## 🏥 Quarantined + +
    +quarantined test + +
    + + + + +
    +
    + +## 🚫 Canceled + +
    +canceled test + +
    + + +
    Retry with bundle exec rspec './spec/foo/bar.rb:14'
    + +
    +
    + diff --git a/internal/captain/reporting/.snapshots/Markdown Report produces a readable summary when cloud is enabled b/internal/captain/reporting/.snapshots/Markdown Report produces a readable summary when cloud is enabled new file mode 100644 index 0000000..bf702b5 --- /dev/null +++ b/internal/captain/reporting/.snapshots/Markdown Report produces a readable summary when cloud is enabled @@ -0,0 +1,142 @@ +# `some-suite-id` Summary + +[🔗 View in RWX](https://example.com/captain/my-slug/test_suite_summaries/some-suite-id/some/branch/abcdef113131) + +9 tests, 1 flaky, 3 failed, 1 timed out, 1 quarantined, 1 canceled, 1 skipped, 2 retries + +## 🔁 Flaky + +
    +flaky test + +
    +
    Retried 1 time
    +
    Defined at some/path/to/file.rb:15
    + + +
    +
    +Failure Details
    + +
    expected true to equal false
    +
    +file/path/one.rb:4
    +file/path/two.rb:4
    +file/path/three.rb:4
    + +
    +
    + +
    +
    + +## ❌ Failed + +
    +failed test + +
    +
    Retried 2 times
    +
    Defined at some/path/to/file.rb
    +
    Retry with bundle exec rspec './spec/foo/bar.rb[4:5:6]'
    + +
    +
    +Failure Details
    + +
    expected true to equal false
    +
    +file/path/one.rb:4
    +file/path/two.rb:4
    +file/path/three.rb:4
    + +
    +
    + +
    +
    +
    +failed test backtrace only + +
    + +
    Defined at some/path/to/file.rb
    +
    Retry with bundle exec rspec './spec/foo/bar.rb[7:8:9]'
    + +
    +
    +Failure Details
    + +
    file/path/one.rb:4
    +file/path/two.rb:4
    +file/path/three.rb:4
    + +
    +
    + +
    +
    +
    +failed test message only w/ ansi + +
    + + +
    Retry with bundle exec rspec './spec/foo/bar.rb:15'
    + +
    +
    +Failure Details
    + +
    Failure/Error: expect(thanos).to eq("inevitable")
    +
    +  expected: "inevitable"
    +       got: "evitable"
    +
    +  (compared using ==)
    + +
    +
    + +
    +
    + +## ⏳ Timed Out + +
    +timed out test + +
    + + +
    Retry with bundle exec rspec './spec/foo/bar.rb:12'
    + +
    +
    + +## 🏥 Quarantined + +
    +quarantined test + +
    + + + + +
    +
    + +## 🚫 Canceled + +
    +canceled test + +
    + + +
    Retry with bundle exec rspec './spec/foo/bar.rb:14'
    + +
    +
    + diff --git a/internal/captain/reporting/.snapshots/Markdown Report produces a readable summary with a custom retry template b/internal/captain/reporting/.snapshots/Markdown Report produces a readable summary with a custom retry template new file mode 100644 index 0000000..0418f7d --- /dev/null +++ b/internal/captain/reporting/.snapshots/Markdown Report produces a readable summary with a custom retry template @@ -0,0 +1,140 @@ +# `some-suite-id` Summary + +9 tests, 1 flaky, 3 failed, 1 timed out, 1 quarantined, 1 canceled, 1 skipped, 2 retries + +## 🔁 Flaky + +
    +flaky test + +
    +
    Retried 1 time
    +
    Defined at some/path/to/file.rb:15
    + + +
    +
    +Failure Details
    + +
    expected true to equal false
    +
    +file/path/one.rb:4
    +file/path/two.rb:4
    +file/path/three.rb:4
    + +
    +
    + +
    +
    + +## ❌ Failed + +
    +failed test + +
    +
    Retried 2 times
    +
    Defined at some/path/to/file.rb
    +
    Retry with bin/rspec './spec/foo/bar.rb[4:5:6]'
    + +
    +
    +Failure Details
    + +
    expected true to equal false
    +
    +file/path/one.rb:4
    +file/path/two.rb:4
    +file/path/three.rb:4
    + +
    +
    + +
    +
    +
    +failed test backtrace only + +
    + +
    Defined at some/path/to/file.rb
    +
    Retry with bin/rspec './spec/foo/bar.rb[7:8:9]'
    + +
    +
    +Failure Details
    + +
    file/path/one.rb:4
    +file/path/two.rb:4
    +file/path/three.rb:4
    + +
    +
    + +
    +
    +
    +failed test message only w/ ansi + +
    + + +
    Retry with bin/rspec './spec/foo/bar.rb:15'
    + +
    +
    +Failure Details
    + +
    Failure/Error: expect(thanos).to eq("inevitable")
    +
    +  expected: "inevitable"
    +       got: "evitable"
    +
    +  (compared using ==)
    + +
    +
    + +
    +
    + +## ⏳ Timed Out + +
    +timed out test + +
    + + +
    Retry with bin/rspec './spec/foo/bar.rb:12'
    + +
    +
    + +## 🏥 Quarantined + +
    +quarantined test + +
    + + + + +
    +
    + +## 🚫 Canceled + +
    +canceled test + +
    + + +
    Retry with bin/rspec './spec/foo/bar.rb:14'
    + +
    +
    + diff --git a/internal/captain/reporting/.snapshots/Markdown Report produces a truncated summary <= 1MB b/internal/captain/reporting/.snapshots/Markdown Report produces a truncated summary <= 1MB new file mode 100644 index 0000000..609a6c7 --- /dev/null +++ b/internal/captain/reporting/.snapshots/Markdown Report produces a truncated summary <= 1MB @@ -0,0 +1,99 @@ +# `some-suite-id` Summary + +100 tests, 100 failed + +## ❌ Failed + +
    +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +
    + + +
    Retry with bundle exec rspec '0'
    + +
    +
    +
    +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +
    + + +
    Retry with bundle exec rspec '1'
    + +
    +
    +
    +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +
    + + +
    Retry with bundle exec rspec '2'
    + +
    +
    +
    +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +
    + + +
    Retry with bundle exec rspec '3'
    + +
    +
    +
    +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +
    + + +
    Retry with bundle exec rspec '4'
    + +
    +
    +
    +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +
    + + +
    Retry with bundle exec rspec '5'
    + +
    +
    +
    +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +
    + + +
    Retry with bundle exec rspec '6'
    + +
    +
    +
    +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +
    + + +
    Retry with bundle exec rspec '7'
    + +
    +
    +
    +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +
    + + +
    Retry with bundle exec rspec '8'
    + +
    +
    + + +Your results have been truncated; markdown summarization has a 1MB limit. diff --git a/internal/captain/reporting/configuration.go b/internal/captain/reporting/configuration.go new file mode 100644 index 0000000..d351f38 --- /dev/null +++ b/internal/captain/reporting/configuration.go @@ -0,0 +1,12 @@ +package reporting + +import "github.com/rwx-cloud/cli/internal/captain/providers" + +type Configuration struct { + CloudEnabled bool + CloudHost string + CloudOrganizationSlug string + SuiteID string + RetryCommandTemplate string + Provider providers.Provider +} diff --git a/internal/captain/reporting/junit.go b/internal/captain/reporting/junit.go new file mode 100644 index 0000000..fd7631f --- /dev/null +++ b/internal/captain/reporting/junit.go @@ -0,0 +1,106 @@ +package reporting + +import ( + "encoding/xml" + "time" + + "github.com/acarl005/stripansi" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/fs" + "github.com/rwx-cloud/cli/internal/captain/parsing" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +func WriteJUnitSummary(file fs.File, testResults v1.TestResults, _ Configuration) error { + result := parsing.JUnitTestResults{ + TestSuites: make([]parsing.JUnitTestSuite, 0), + } + + finishedAt := time.Time{} + startedAt := time.Time{} + suite := parsing.JUnitTestSuite{} + + suite.Errors = testResults.Summary.OtherErrors + testResults.Summary.TimedOut + suite.Failures = testResults.Summary.Canceled + testResults.Summary.Failed + suite.Skipped = testResults.Summary.Pended + testResults.Summary.Skipped + testResults.Summary.Todo + + for _, test := range testResults.Tests { + testCase := parsing.JUnitTestCase{ + Name: test.Name, + SystemErr: test.Attempt.Stderr, + SystemOut: test.Attempt.Stdout, + } + + if test.Attempt.Duration != nil { + testCase.Time = test.Attempt.Duration.Seconds() + } + + if test.Attempt.StartedAt != nil && test.Attempt.StartedAt.Before(startedAt) { + startedAt = *test.Attempt.StartedAt + } + + if test.Attempt.FinishedAt != nil && test.Attempt.FinishedAt.After(finishedAt) { + finishedAt = *test.Attempt.FinishedAt + } + + if test.Location != nil { + if test.Location.File != "" { + test := test + testCase.File = &test.Location.File + } + + if test.Location.Line != nil { + testCase.Line = test.Location.Line + } + } + + message := test.Attempt.Status.Message + if message != nil { + strippedMessage := stripansi.Strip(*test.Attempt.Status.Message) + message = &strippedMessage + } + + //nolint:exhaustive + switch test.Attempt.Status.Kind { + case v1.TestStatusPended, v1.TestStatusSkipped, v1.TestStatusTodo: + testCase.Skipped = &parsing.JUnitSkipped{ + Message: message, + } + case v1.TestStatusCanceled, v1.TestStatusFailed: + testCase.Failure = &parsing.JUnitFailure{ + Message: message, + } + case v1.TestStatusTimedOut: + testCase.Error = &parsing.JUnitFailure{ + Message: message, + } + } + + suite.TestCases = append(suite.TestCases, testCase) + } + + if !startedAt.IsZero() { + suite.Time = finishedAt.Sub(startedAt).Seconds() + suite.Timestamp = startedAt.String() + } + + totalTests := len(suite.TestCases) + suite.Tests = &totalTests + + result.TestSuites = append(result.TestSuites, suite) + + _, err := file.Write([]byte("\n")) + if err != nil { + return errors.WithStack(err) + } + + encoder := xml.NewEncoder(file) + encoder.Indent("", " ") + + if err := encoder.Encode(result); err != nil { + return errors.WithStack(err) + } + + return nil +} diff --git a/internal/captain/reporting/junit_test.go b/internal/captain/reporting/junit_test.go new file mode 100644 index 0000000..f8b7329 --- /dev/null +++ b/internal/captain/reporting/junit_test.go @@ -0,0 +1,117 @@ +package reporting_test + +import ( + "encoding/xml" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/mocks" + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/reporting" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JUnit Report", func() { + var ( + mockFile *mocks.File + testResults v1.TestResults + ) + + BeforeEach(func() { + mockFile = new(mocks.File) + mockFile.Builder = new(strings.Builder) + + message := "expected true to equal false" + messageWithAnsi := `Failure/Error: expect(thanos).to ` + + `eq("inevitable") + + expected: "inevitable" + got: "evitable" + + (compared using ==)` + + testResults = v1.TestResults{ + Framework: v1.Framework{ + Language: "Ruby", + Kind: "RSpec", + }, + Summary: v1.Summary{ + Status: v1.SummaryStatusSuccessful, + Tests: 1, + OtherErrors: 2, + Retries: 3, + Canceled: 4, + Failed: 5, + Pended: 6, + Quarantined: 7, + Skipped: 8, + Successful: 9, + TimedOut: 10, + Todo: 11, + }, + Tests: []v1.Test{ + { + Name: "name of the test", + Attempt: v1.TestAttempt{ + Duration: nil, + Status: v1.TestStatus{Kind: "successful"}, + }, + Location: &v1.Location{ + File: "/path/to/file", + Line: new(int), + }, + }, + { + Name: "failed test message only w/o ansi", + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus( + &message, + nil, + nil, + ), + }, + }, + { + Name: "failed test message only w/ ansi", + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus( + &messageWithAnsi, + nil, + nil, + ), + }, + }, + }, + } + }) + + It("produces a parsable JUnit file", func() { + var result parsing.JUnitTestResults + + Expect(reporting.WriteJUnitSummary(mockFile, testResults, reporting.Configuration{})).To(Succeed()) + Expect(xml.Unmarshal([]byte(mockFile.Builder.String()), &result)).To(Succeed()) + Expect(result.TestSuites).To(HaveLen(1)) + + Expect(result.TestSuites[0].Errors).To(Equal(12)) + Expect(result.TestSuites[0].Failures).To(Equal(9)) + Expect(result.TestSuites[0].Skipped).To(Equal(25)) + + Expect(result.TestSuites[0].TestCases).To(HaveLen(3)) + Expect(result.TestSuites[0].TestCases[0].Name).To(Equal("name of the test")) + Expect(*result.TestSuites[0].TestCases[0].File).To(Equal("/path/to/file")) + Expect(*result.TestSuites[0].TestCases[0].Line).To(Equal(0)) + + Expect(result.TestSuites[0].TestCases[1].Name).To(Equal("failed test message only w/o ansi")) + Expect(*result.TestSuites[0].TestCases[1].Failure.Message).To(Equal("expected true to equal false")) + + Expect(result.TestSuites[0].TestCases[2].Name).To(Equal("failed test message only w/ ansi")) + Expect(*result.TestSuites[0].TestCases[2].Failure.Message).To(Equal(`Failure/Error: expect(thanos).to eq("inevitable") + + expected: "inevitable" + got: "evitable" + + (compared using ==)`)) + }) +}) diff --git a/internal/captain/reporting/markdown.go b/internal/captain/reporting/markdown.go new file mode 100644 index 0000000..0e03439 --- /dev/null +++ b/internal/captain/reporting/markdown.go @@ -0,0 +1,465 @@ +package reporting + +import ( + "fmt" + "strings" + "text/template" + + "github.com/acarl005/stripansi" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/fs" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type markdownTestSection string + +var ( + flakySection markdownTestSection = "🔁 Flaky" + failedSection markdownTestSection = "❌ Failed" + timedOutSection markdownTestSection = "⏳ Timed Out" + quarantinedSection markdownTestSection = "🏥 Quarantined" + canceledSection markdownTestSection = "🚫 Canceled" +) + +type markdownTest struct { + Name string + Location string + Command string + Message *string + Backtrace string + Retries int +} + +const ( + oneMB = 1000000 + markdownResultsTruncated = "\n\nYour results have been truncated; markdown summarization has a 1MB limit." + markdownTestTemplate = `
    +{{ .Name }} + +
    +{{ if .Retries }}
    Retried {{ .Retries}} time{{ if ne .Retries 1 }}s{{end}}
    {{ end }} +{{ if .Location }}
    Defined at {{ .Location }}
    {{ end }} +{{ if .Command }}
    Retry with {{ .Command }}
    {{ end }} +{{ if or .Message .Backtrace }} +
    +
    +Failure Details
    +{{ if and .Message .Backtrace }} +
    {{ .Message}}
    +
    +{{ .Backtrace }}
    +{{ else }} +
    {{ or .Message .Backtrace }}
    +{{ end }} +
    +
    +{{ end }} +
    +
    +` +) + +func WriteMarkdownSummary(file fs.File, testResults v1.TestResults, cfg Configuration) error { + markdown := new(strings.Builder) + if _, err := fmt.Fprintf(markdown, "# `%v` Summary\n\n", cfg.SuiteID); err != nil { + return errors.WithStack(err) + } + + if cfg.CloudEnabled { + if _, err := fmt.Fprintf( + markdown, + "[🔗 View in RWX](https://%v/captain/%v/test_suite_summaries/%v/%v/%v)\n\n", + cfg.CloudHost, + cfg.CloudOrganizationSlug, + cfg.SuiteID, + cfg.Provider.BranchName, + cfg.Provider.CommitSha, + ); err != nil { + return errors.WithStack(err) + } + } + + if err := writeMarkdownSummaryLine(markdown, testResults); err != nil { + return errors.WithStack(err) + } + + testsBySection := testsByMarkdownSection(testResults) + writersBySection := map[markdownTestSection]func( + *strings.Builder, + v1.Framework, + []v1.Test, + Configuration, + ) (bool, error){ + flakySection: writeMarkdownFlakySection, + failedSection: writeMarkdownFailedSection, + timedOutSection: writeMarkdownTimedOutSection, + quarantinedSection: writeMarkdownQuarantinedSection, + canceledSection: writeMarkdownCanceledSection, + } + + orderedSections := []markdownTestSection{ + flakySection, + failedSection, + timedOutSection, + quarantinedSection, + canceledSection, + } + + for _, section := range orderedSections { + shouldTruncate, err := writersBySection[section](markdown, testResults.Framework, testsBySection[section], cfg) + if err != nil { + return errors.WithStack(err) + } + if shouldTruncate { + if _, err := markdown.WriteString(markdownResultsTruncated); err != nil { + return errors.WithStack(err) + } + if _, err := file.Write([]byte(markdown.String())); err != nil { + return errors.WithStack(err) + } + return nil + } + } + + if _, err := file.Write([]byte(markdown.String())); err != nil { + return errors.WithStack(err) + } + + return nil +} + +func writeMarkdownSummaryStatus(markdown *strings.Builder, value int, singular string, plural string) error { + if value <= 0 { + return nil + } + + if _, err := fmt.Fprintf(markdown, ", %v %v", value, pluralize(value, singular, plural)); err != nil { + return errors.WithStack(err) + } + + return nil +} + +func writeMarkdownSummaryLine(markdown *strings.Builder, testResults v1.TestResults) error { + summary := testResults.Summary + + if _, err := fmt.Fprintf( + markdown, "%v %v", summary.Tests, pluralize(summary.Tests, "test", "tests"), + ); err != nil { + return errors.WithStack(err) + } + + if err := writeMarkdownSummaryStatus(markdown, summary.Flaky, "flaky", "flaky"); err != nil { + return errors.WithStack(err) + } + + if err := writeMarkdownSummaryStatus(markdown, summary.Failed, "failed", "failed"); err != nil { + return errors.WithStack(err) + } + + if err := writeMarkdownSummaryStatus(markdown, summary.TimedOut, "timed out", "timed out"); err != nil { + return errors.WithStack(err) + } + + if err := writeMarkdownSummaryStatus(markdown, summary.Quarantined, "quarantined", "quarantined"); err != nil { + return errors.WithStack(err) + } + + if err := writeMarkdownSummaryStatus(markdown, summary.Canceled, "canceled", "canceled"); err != nil { + return errors.WithStack(err) + } + + if err := writeMarkdownSummaryStatus(markdown, summary.Pended, "pended", "pended"); err != nil { + return errors.WithStack(err) + } + + if err := writeMarkdownSummaryStatus(markdown, summary.Skipped, "skipped", "skipped"); err != nil { + return errors.WithStack(err) + } + + if err := writeMarkdownSummaryStatus(markdown, summary.Todo, "todo", "todo"); err != nil { + return errors.WithStack(err) + } + + if err := writeMarkdownSummaryStatus(markdown, summary.Retries, "retry", "retries"); err != nil { + return errors.WithStack(err) + } + + if _, err := markdown.WriteString("\n"); err != nil { + return errors.WithStack(err) + } + + return nil +} + +func testsByMarkdownSection(testResults v1.TestResults) map[markdownTestSection][]v1.Test { + testsBySection := map[markdownTestSection][]v1.Test{ + flakySection: make([]v1.Test, 0), + failedSection: make([]v1.Test, 0), + timedOutSection: make([]v1.Test, 0), + quarantinedSection: make([]v1.Test, 0), + canceledSection: make([]v1.Test, 0), + } + + for _, test := range testResults.Tests { + // Flaky first so that anything that's flaky will end up only in that section. + // The rest are mutually exclusive + if test.Flaky() { + testsBySection[flakySection] = append(testsBySection[flakySection], test) + continue + } + + if test.Attempt.Status.Kind == v1.TestStatusFailed { + testsBySection[failedSection] = append(testsBySection[failedSection], test) + continue + } + + if test.Attempt.Status.Kind == v1.TestStatusTimedOut { + testsBySection[timedOutSection] = append(testsBySection[timedOutSection], test) + continue + } + + if test.Attempt.Status.Kind == v1.TestStatusQuarantined { + testsBySection[quarantinedSection] = append(testsBySection[quarantinedSection], test) + continue + } + + if test.Attempt.Status.Kind == v1.TestStatusCanceled { + testsBySection[canceledSection] = append(testsBySection[canceledSection], test) + continue + } + } + + return testsBySection +} + +func writeMarkdownFlakySection( + markdown *strings.Builder, + framework v1.Framework, + tests []v1.Test, + cfg Configuration, +) (bool, error) { + return writeMarkdownSection( + markdown, + flakySection, + framework, + tests, + func(test v1.Test) *v1.TestStatus { + if test.Attempt.Status.PotentiallyFlaky() { + return &test.Attempt.Status + } + + for _, attempt := range test.PastAttempts { + if attempt.Status.PotentiallyFlaky() { + return &attempt.Status + } + } + + return nil + }, + cfg, + ) +} + +func writeMarkdownFailedSection( + markdown *strings.Builder, + framework v1.Framework, + tests []v1.Test, + cfg Configuration, +) (bool, error) { + return writeMarkdownSection( + markdown, + failedSection, + framework, + tests, + func(test v1.Test) *v1.TestStatus { + return &test.Attempt.Status + }, + cfg, + ) +} + +func writeMarkdownTimedOutSection( + markdown *strings.Builder, + framework v1.Framework, + tests []v1.Test, + cfg Configuration, +) (bool, error) { + return writeMarkdownSection( + markdown, + timedOutSection, + framework, + tests, + func(test v1.Test) *v1.TestStatus { + return &test.Attempt.Status + }, + cfg, + ) +} + +func writeMarkdownQuarantinedSection( + markdown *strings.Builder, + framework v1.Framework, + tests []v1.Test, + cfg Configuration, +) (bool, error) { + return writeMarkdownSection( + markdown, + quarantinedSection, + framework, + tests, + func(test v1.Test) *v1.TestStatus { + return test.Attempt.Status.OriginalStatus + }, + cfg, + ) +} + +func writeMarkdownCanceledSection( + markdown *strings.Builder, + framework v1.Framework, + tests []v1.Test, + cfg Configuration, +) (bool, error) { + return writeMarkdownSection( + markdown, + canceledSection, + framework, + tests, + func(test v1.Test) *v1.TestStatus { + return &test.Attempt.Status + }, + cfg, + ) +} + +func writeMarkdownSection( + markdown *strings.Builder, + section markdownTestSection, + framework v1.Framework, + tests []v1.Test, + findFailedStatus func(v1.Test) *v1.TestStatus, + cfg Configuration, +) (bool, error) { + if len(tests) == 0 { + return false, nil + } + + if _, err := fmt.Fprintf(markdown, "\n## %v\n\n", section); err != nil { + return false, errors.WithStack(err) + } + + parsedTemplate, err := template.New("markdownTestTemplate").Parse(markdownTestTemplate) + if err != nil { + return false, errors.WithStack(err) + } + + retryTemplate, substitution := retryTemplateAndSubstitutionFor(framework, cfg.RetryCommandTemplate) + + for _, test := range tests { + location := "" + if test.Location != nil { + location = test.Location.String() + } + + retryCommand := "" + if retryTemplate != nil && substitution != nil { + substitutions, _ := substitution.SubstitutionsFor( + *retryTemplate, + *v1.NewTestResults(framework, []v1.Test{test}, nil), + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + + if len(substitutions) >= 1 { + retryCommand = retryTemplate.Substitute(substitutions[0]) + } + } + failedStatus := findFailedStatus(test) + markdownTest := markdownTest{ + Name: test.Name, + Location: location, + Command: retryCommand, + Retries: len(test.PastAttempts), + } + if failedStatus != nil { + markdownTest.Backtrace = stripansi.Strip(strings.Join(failedStatus.Backtrace, "\n")) + if failedStatus.Message != nil { + strippedMessage := stripansi.Strip(*failedStatus.Message) + markdownTest.Message = &strippedMessage + } + } + + testMarkdown := new(strings.Builder) + if err := parsedTemplate.Execute(testMarkdown, markdownTest); err != nil { + return false, errors.WithStack(err) + } + + if oneMB-markdown.Len()-testMarkdown.Len()-len(markdownResultsTruncated) <= 0 { + return true, nil + } + + if _, err := markdown.WriteString(testMarkdown.String()); err != nil { + return false, errors.WithStack(err) + } + } + + return false, nil +} + +func retryTemplateAndSubstitutionFor( + framework v1.Framework, + retryCommandTemplate string, +) (*templating.CompiledTemplate, targetedretries.Substitution) { + // note we don't propagate any errors up here; if we're unable to generate + // the retry command, we should still provide the rest of the summary + + substitution, ok := targetedretries.SubstitutionsByFramework[framework] + if !ok { + return nil, nil + } + + var exampleTemplate *templating.CompiledTemplate + if t, err := templating.CompileTemplate(substitution.Example()); err == nil { + exampleTemplate = &t + } + + var providedTemplate *templating.CompiledTemplate + if retryCommandTemplate != "" { + if t, err := templating.CompileTemplate(retryCommandTemplate); err == nil { + providedTemplate = &t + } + } + + if exampleTemplate != nil { + if err := substitution.ValidateTemplate(*exampleTemplate); err != nil { + exampleTemplate = nil + } + } + + if providedTemplate != nil { + if err := substitution.ValidateTemplate(*providedTemplate); err != nil { + providedTemplate = nil + } + } + + if providedTemplate != nil { + return providedTemplate, substitution + } + + if exampleTemplate != nil { + return exampleTemplate, substitution + } + + return nil, nil +} + +func pluralize(count int, singular string, plural string) string { + if count == 1 { + return singular + } + + return plural +} diff --git a/internal/captain/reporting/markdown_test.go b/internal/captain/reporting/markdown_test.go new file mode 100644 index 0000000..a82e447 --- /dev/null +++ b/internal/captain/reporting/markdown_test.go @@ -0,0 +1,212 @@ +package reporting_test + +import ( + "strconv" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/mocks" + "github.com/rwx-cloud/cli/internal/captain/providers" + "github.com/rwx-cloud/cli/internal/captain/reporting" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Markdown Report", func() { + var ( + mockFile *mocks.File + testResults v1.TestResults + ) + + BeforeEach(func() { + mockFile = new(mocks.File) + mockFile.Builder = new(strings.Builder) + + id1 := "./spec/foo/bar.rb[1:2:3]" + id2 := "./spec/foo/bar.rb[4:5:6]" + id3 := "./spec/foo/bar.rb[7:8:9]" + id4 := "./spec/foo/bar.rb:12" + id5 := "./spec/foo/bar.rb:11" + id6 := "./spec/foo/bar.rb:12" + id7 := "./spec/foo/bar.rb:13" + id8 := "./spec/foo/bar.rb:14" + id9 := "./spec/foo/bar.rb:15" + message := "expected true to equal false" + messageWithAnsi := `Failure/Error: expect(thanos).to ` + + `eq("inevitable") + + expected: "inevitable" + got: "evitable" + + (compared using ==)` + fifteen := 15 + testResults = *v1.NewTestResults( + v1.RubyRSpecFramework, + []v1.Test{ + { + ID: &id1, + Name: "successful test", + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + ID: &id2, + Name: "failed test", + Location: &v1.Location{File: "some/path/to/file.rb"}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus( + &message, + nil, + []string{"file/path/one.rb:4", "file/path/two.rb:4", "file/path/three.rb:4"}, + ), + }, + PastAttempts: []v1.TestAttempt{ + {Status: v1.NewFailedTestStatus(nil, nil, nil)}, + {Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + }, + { + ID: &id3, + Name: "failed test backtrace only", + Location: &v1.Location{File: "some/path/to/file.rb"}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus( + nil, + nil, + []string{"file/path/one.rb:4", "file/path/two.rb:4", "file/path/three.rb:4"}, + ), + }, + }, + { + ID: &id4, + Name: "flaky test", + Location: &v1.Location{File: "some/path/to/file.rb", Line: &fifteen}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + PastAttempts: []v1.TestAttempt{ + { + Status: v1.NewFailedTestStatus( + &message, + nil, + []string{"file/path/one.rb:4", "file/path/two.rb:4", "file/path/three.rb:4"}, + ), + }, + }, + }, + { + ID: &id5, + Name: "skipped test", + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + { + ID: &id6, + Name: "timed out test", + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + ID: &id7, + Name: "quarantined test", + Attempt: v1.TestAttempt{Status: v1.NewQuarantinedTestStatus(v1.NewTimedOutTestStatus(nil, nil, nil))}, + }, + { + ID: &id8, + Name: "canceled test", + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + ID: &id9, + Name: "failed test message only w/ ansi", + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus( + &messageWithAnsi, + nil, + nil, + ), + }, + }, + }, + nil, + ) + }) + + It("produces a readable summary when cloud is enabled", func() { + cfg := reporting.Configuration{ + SuiteID: "some-suite-id", + CloudEnabled: true, + CloudHost: "example.com", + CloudOrganizationSlug: "my-slug", + + Provider: providers.Provider{ + BranchName: "some/branch", + CommitSha: "abcdef113131", + }, + } + Expect(reporting.WriteMarkdownSummary(mockFile, testResults, cfg)).To(Succeed()) + summary := mockFile.String() + cupaloy.SnapshotT(GinkgoT(), summary) + }) + + It("produces a readable summary with a custom retry template", func() { + cfg := reporting.Configuration{ + SuiteID: "some-suite-id", + CloudEnabled: false, + CloudHost: "", + RetryCommandTemplate: "bin/rspec {{ tests }}", + Provider: providers.Provider{}, + } + Expect(reporting.WriteMarkdownSummary(mockFile, testResults, cfg)).To(Succeed()) + summary := mockFile.String() + cupaloy.SnapshotT(GinkgoT(), summary) + }) + + It("produces a readable summary when cloud is disabled", func() { + cfg := reporting.Configuration{ + SuiteID: "some-suite-id", + CloudEnabled: false, + CloudHost: "", + Provider: providers.Provider{}, + } + Expect(reporting.WriteMarkdownSummary(mockFile, testResults, cfg)).To(Succeed()) + summary := mockFile.String() + cupaloy.SnapshotT(GinkgoT(), summary) + }) + + It("produces a truncated summary <= 1MB", func() { + cfg := reporting.Configuration{ + SuiteID: "some-suite-id", + CloudEnabled: false, + CloudHost: "", + Provider: providers.Provider{}, + } + + hundredKBName := new(strings.Builder) + for i := 0; i < 100000; i++ { + _, err := hundredKBName.WriteString("a") + Expect(err).NotTo(HaveOccurred()) + } + + tests := make([]v1.Test, 0) + for i := 0; i < 100; i++ { + id := strconv.Itoa(i) + tests = append(tests, v1.Test{ + ID: &id, + Name: hundredKBName.String(), + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }) + } + + Expect( + reporting.WriteMarkdownSummary( + mockFile, + *v1.NewTestResults(v1.RubyRSpecFramework, tests, nil), + cfg, + ), + ).To(Succeed()) + summary := mockFile.String() + cupaloy.SnapshotT(GinkgoT(), summary) + + Expect(len(summary) < 1000000).To(Equal(true)) + Expect(strings.Count(summary, hundredKBName.String()) < 10).To(Equal(true)) + }) +}) diff --git a/internal/captain/reporting/reporting_suite_test.go b/internal/captain/reporting/reporting_suite_test.go new file mode 100644 index 0000000..856f158 --- /dev/null +++ b/internal/captain/reporting/reporting_suite_test.go @@ -0,0 +1,15 @@ +package reporting_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestReporting(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "Reporting Suite") +} diff --git a/internal/captain/reporting/rwx.go b/internal/captain/reporting/rwx.go new file mode 100644 index 0000000..d5f3958 --- /dev/null +++ b/internal/captain/reporting/rwx.go @@ -0,0 +1,20 @@ +package reporting + +import ( + "encoding/json" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/fs" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +func WriteJSONSummary(file fs.File, testResults v1.TestResults, _ Configuration) error { + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + + if err := encoder.Encode(testResults); err != nil { + return errors.WithStack(err) + } + + return nil +} diff --git a/internal/captain/reporting/text.go b/internal/captain/reporting/text.go new file mode 100644 index 0000000..2edea07 --- /dev/null +++ b/internal/captain/reporting/text.go @@ -0,0 +1,125 @@ +package reporting + +import ( + "fmt" + "io" + "strings" + + "golang.org/x/text/cases" + "golang.org/x/text/language" + + "github.com/rwx-cloud/cli/internal/captain/errors" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +var detailedTestStatusKinds = []v1.TestStatusKind{ + v1.TestStatusFailed, + v1.TestStatusTimedOut, + v1.TestStatusCanceled, +} + +var orderedTestStatusKinds = []v1.TestStatusKind{ + v1.TestStatusSuccessful, + v1.TestStatusFailed, + v1.TestStatusTimedOut, + v1.TestStatusCanceled, + v1.TestStatusQuarantined, + v1.TestStatusSkipped, + v1.TestStatusPended, + v1.TestStatusTodo, +} + +var titleCaser = cases.Title(language.AmericanEnglish) + +func WriteTextSummary(w io.Writer, testResults v1.TestResults, _ Configuration) error { + statuses := summarizeTestsByStatus(testResults) + totalTests := testResults.Summary.Tests + + for _, kind := range detailedTestStatusKinds { + tests, ok := statuses[kind] + if !ok { + continue + } + + statusName := titleCaser.String(testStatusKindToString(kind)) + _, err := fmt.Fprintf(w, "\n%s (%d):\n", statusName, len(statuses[kind])) + if err != nil { + return errors.WithStack(err) + } + + for _, testName := range tests { + _, err := fmt.Fprintf(w, "- %s\n", testName) + if err != nil { + return errors.WithStack(err) + } + } + } + + totalCountParts := make([]string, 0, len(orderedTestStatusKinds)) + if testResults.Summary.Successful > 0 { + totalCountParts = append(totalCountParts, fmt.Sprintf("%d %s", testResults.Summary.Successful, "successful")) + } + + for _, kind := range orderedTestStatusKinds { + tests, ok := statuses[kind] + if !ok || len(tests) == 0 { + continue + } + + totalCountParts = append(totalCountParts, fmt.Sprintf("%d %s", len(tests), testStatusKindToString(kind))) + } + + pluralizeTests := "tests" + if totalTests == 1 { + pluralizeTests = "test" + } + + _, err := fmt.Fprintf(w, "\n%d total %s", totalTests, pluralizeTests) + if err != nil { + return errors.WithStack(err) + } + + if len(totalCountParts) > 0 { + _, err = fmt.Fprintf(w, ": %s\n", strings.Join(totalCountParts, ", ")) + if err != nil { + return errors.WithStack(err) + } + } else { + _, err = fmt.Fprint(w, "\n") + if err != nil { + return errors.WithStack(err) + } + } + + return nil +} + +func summarizeTestsByStatus(testResults v1.TestResults) map[v1.TestStatusKind][]string { + statuses := make(map[v1.TestStatusKind][]string) + + for _, test := range testResults.Tests { + if test.Attempt.Status.Kind == v1.TestStatusSuccessful { + continue + } + + tests, ok := statuses[test.Attempt.Status.Kind] + if !ok { + tests = []string{test.Name} + } else { + tests = append(tests, test.Name) + } + + statuses[test.Attempt.Status.Kind] = tests + } + + return statuses +} + +func testStatusKindToString(kind v1.TestStatusKind) string { + switch kind { //nolint:exhaustive + case v1.TestStatusTimedOut: + return "timed out" + default: + return string(kind) + } +} diff --git a/internal/captain/reporting/text_test.go b/internal/captain/reporting/text_test.go new file mode 100644 index 0000000..4c7f2d4 --- /dev/null +++ b/internal/captain/reporting/text_test.go @@ -0,0 +1,75 @@ +package reporting_test + +import ( + "strings" + + "github.com/rwx-cloud/cli/internal/captain/mocks" + "github.com/rwx-cloud/cli/internal/captain/reporting" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Text Report", func() { + var ( + mockFile *mocks.File + testResults v1.TestResults + ) + + BeforeEach(func() { + mockFile = new(mocks.File) + mockFile.Builder = new(strings.Builder) + + testResults = v1.TestResults{ + Summary: v1.Summary{ + Status: v1.SummaryStatusFailed, + Tests: 4, + Failed: 1, + Skipped: 1, + Successful: 1, + TimedOut: 1, + }, + Tests: []v1.Test{ + { + Name: "successful test", + Attempt: v1.TestAttempt{ + Duration: nil, + Status: v1.TestStatus{Kind: v1.TestStatusSuccessful}, + }, + }, + { + Name: "failed test", + Attempt: v1.TestAttempt{ + Duration: nil, + Status: v1.TestStatus{Kind: v1.TestStatusFailed}, + }, + }, + { + Name: "skipped test", + Attempt: v1.TestAttempt{ + Duration: nil, + Status: v1.TestStatus{Kind: v1.TestStatusSkipped}, + }, + }, + { + Name: "timed out test", + Attempt: v1.TestAttempt{ + Duration: nil, + Status: v1.TestStatus{Kind: v1.TestStatusTimedOut}, + }, + }, + }, + } + }) + + It("produces a readable summary", func() { + Expect(reporting.WriteTextSummary(mockFile, testResults, reporting.Configuration{})).To(Succeed()) + summary := mockFile.String() + + Expect(summary).To(ContainSubstring("Failed (1):")) + Expect(summary).To(ContainSubstring("Timed Out (1):")) + Expect(summary).NotTo(ContainSubstring("Skipped (1):")) + Expect(summary).To(ContainSubstring("4 total tests: 1 successful, 1 failed, 1 timed out, 1 skipped")) + }) +}) diff --git a/internal/captain/runpartition/delimiter_substitution.go b/internal/captain/runpartition/delimiter_substitution.go new file mode 100644 index 0000000..0db7279 --- /dev/null +++ b/internal/captain/runpartition/delimiter_substitution.go @@ -0,0 +1,51 @@ +package runpartition + +import ( + "fmt" + "sort" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" +) + +type DelimiterSubstitution struct { + Delimiter string +} + +func (s DelimiterSubstitution) Example() string { + return "your-test-command {{ testFiles }}" +} + +func (s DelimiterSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + message := "Partitioning with a delimiter requires a template with only the 'testFiles' keyword" + + if len(keywords) == 0 { + return errors.NewInputError("%v; no keywords were found", message) + } + + if len(keywords) > 1 { + sort.Strings(keywords) + return errors.NewInputError("%v; these were found: %v", message, strings.Join(keywords, ", ")) + } + + if keywords[0] != "testFiles" { + return errors.NewInputError("%v; '%v' was found instead", message, keywords[0]) + } + + return nil +} + +func (s DelimiterSubstitution) SubstitutionLookupFor( + _ templating.CompiledTemplate, + testFilePaths []string, +) (map[string]string, error) { + escapedTestFilePaths := make([]string, 0) + + for _, testFilePath := range testFilePaths { + escapedTestFilePaths = append(escapedTestFilePaths, fmt.Sprintf("'%v'", templating.ShellEscape(testFilePath))) + } + + return map[string]string{"testFiles": strings.Join(escapedTestFilePaths, s.Delimiter)}, nil +} diff --git a/internal/captain/runpartition/delimiter_substitution_test.go b/internal/captain/runpartition/delimiter_substitution_test.go new file mode 100644 index 0000000..0cff1c6 --- /dev/null +++ b/internal/captain/runpartition/delimiter_substitution_test.go @@ -0,0 +1,104 @@ +package runpartition_test + +import ( + "github.com/rwx-cloud/cli/internal/captain/runpartition" + "github.com/rwx-cloud/cli/internal/captain/templating" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("DelimiterSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution runpartition.Substitution = runpartition.DelimiterSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := runpartition.DelimiterSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := runpartition.DelimiterSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("nothing") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("no keywords were found")) + }) + + It("is invalid for a template without multiple placeholders", func() { + substitution := runpartition.DelimiterSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("some-command {{ a }} {{ b }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("these were found: a, b")) + }) + + It("is invalid for a template with one incorrect placeholders", func() { + substitution := runpartition.DelimiterSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("some-command {{ testFilesNope }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("'testFilesNope' was found instead")) + }) + + It("is valid exactly testFiles is provided", func() { + substitution := runpartition.DelimiterSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("some-command {{ testFiles }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("SubstitutionLookupFor", func() { + Context("when provided no files", func() { + It("returns testFiles as an empty string", func() { + substitution := runpartition.DelimiterSubstitution{} + compiledTemplate, _ := templating.CompileTemplate("some-command {{ testFiles }}") + lookup, err := substitution.SubstitutionLookupFor(compiledTemplate, []string{}) + + Expect(err).NotTo(HaveOccurred()) + Expect(lookup["testFiles"]).To(Equal("")) + }) + }) + + Context("when provided many files", func() { + It("returns testFiles separated with delimiter", func() { + substitution := runpartition.DelimiterSubstitution{Delimiter: " || "} + compiledTemplate, _ := templating.CompileTemplate("some-command {{ testFiles }}") + lookup, err := substitution.SubstitutionLookupFor(compiledTemplate, []string{"a", "b", "c"}) + + Expect(err).NotTo(HaveOccurred()) + Expect(lookup["testFiles"]).To(Equal("'a' || 'b' || 'c'")) + Expect(len(lookup)).To(Equal(1)) + }) + }) + + Context("when file includes a space", func() { + It("gets escaped", func() { + substitution := runpartition.DelimiterSubstitution{Delimiter: ","} + compiledTemplate, _ := templating.CompileTemplate("some-command {{ testFiles }}") + lookup, err := substitution.SubstitutionLookupFor(compiledTemplate, []string{"a spec", "b spec"}) + + Expect(err).NotTo(HaveOccurred()) + Expect(lookup["testFiles"]).To(Equal("'a spec','b spec'")) + }) + }) + }) +}) diff --git a/internal/captain/runpartition/runpartition_suite_test.go b/internal/captain/runpartition/runpartition_suite_test.go new file mode 100644 index 0000000..2fcac38 --- /dev/null +++ b/internal/captain/runpartition/runpartition_suite_test.go @@ -0,0 +1,15 @@ +package runpartition_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestPartition(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "Run Partition Suite") +} diff --git a/internal/captain/runpartition/substitution.go b/internal/captain/runpartition/substitution.go new file mode 100644 index 0000000..952a002 --- /dev/null +++ b/internal/captain/runpartition/substitution.go @@ -0,0 +1,14 @@ +package runpartition + +import ( + "github.com/rwx-cloud/cli/internal/captain/templating" +) + +type Substitution interface { + Example() string + ValidateTemplate(compiledTemplate templating.CompiledTemplate) error + SubstitutionLookupFor( + _ templating.CompiledTemplate, + testFilePaths []string, + ) (map[string]string, error) +} diff --git a/internal/captain/targetedretries/.snapshots/DotNetxUnitSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/DotNetxUnitSubstitution works with a real file new file mode 100644 index 0000000..4cf8d98 --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/DotNetxUnitSubstitution works with a real file @@ -0,0 +1,5 @@ +([]map[string]string) (len=1) { + (map[string]string) (len=1) { + (string) (len=6) "filter": (string) (len=74) "FullyQualifiedName=CommandLineTests+MethodArgument.MethodArgumentNotPassed" + } +} diff --git a/internal/captain/targetedretries/.snapshots/ElixirExUnitSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/ElixirExUnitSubstitution works with a real file new file mode 100644 index 0000000..2d58615 --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/ElixirExUnitSubstitution works with a real file @@ -0,0 +1,8 @@ +([]map[string]string) (len=2) { + (map[string]string) (len=1) { + (string) (len=5) "tests": (string) (len=51) "'test/exunitexample_web/views/exception_test.exs:7'" + }, + (map[string]string) (len=1) { + (string) (len=5) "tests": (string) (len=49) "'test/exunitexample_web/views/failing_test.exs:7'" + } +} diff --git a/internal/captain/targetedretries/.snapshots/GoGinkgoSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/GoGinkgoSubstitution works with a real file new file mode 100644 index 0000000..1925245 --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/GoGinkgoSubstitution works with a real file @@ -0,0 +1,5 @@ +([]map[string]string) (len=1) { + (map[string]string) (len=1) { + (string) (len=5) "tests": (string) (len=1247) "--focus-file '/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:53' --focus-file '/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:19' --focus-file '/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:32' --focus-file '/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:42' --focus-file '/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go:33' --focus-file '/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go:52' --focus-file '/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:34' --focus-file '/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:15' --focus-file '/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:24' --focus-file '/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:24' --focus-file '/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:38' --focus-file '/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:47' --focus-file '/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:57'" + } +} diff --git a/internal/captain/targetedretries/.snapshots/GoTestSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/GoTestSubstitution works with a real file new file mode 100644 index 0000000..b57721b --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/GoTestSubstitution works with a real file @@ -0,0 +1,10 @@ +([]map[string]string) (len=2) { + (map[string]string) (len=2) { + (string) (len=7) "package": (string) (len=52) "github.com/captain-examples/go-testing/internal/pkg1", + (string) (len=3) "run": (string) (len=48) "^TestBarFailing|TestFooFailing|TestTableFailing$" + }, + (map[string]string) (len=2) { + (string) (len=7) "package": (string) (len=52) "github.com/captain-examples/go-testing/internal/pkg2", + (string) (len=3) "run": (string) (len=21) "^TestWhateverFailing$" + } +} diff --git a/internal/captain/targetedretries/.snapshots/JSONSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/JSONSubstitution works with a real file new file mode 100644 index 0000000..5d1726c --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/JSONSubstitution works with a real file @@ -0,0 +1,2343 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "RSpec" + }, + "summary": { + "status": { + "kind": "failed" + }, + "tests": 36, + "flaky": 0, + "otherErrors": 0, + "retries": 0, + "canceled": 0, + "failed": 36, + "pended": 0, + "quarantined": 0, + "skipped": 0, + "successful": 0, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "id": "./spec/examples/class_spec.rb[1:2]", + "name": "Tests::Case has top-level failing tests", + "lineage": [ + "Tests::Case", + "has top-level failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 6114000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 9 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/class_spec.rb:10:in `block (2 levels) in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:3]", + "name": "Tests::Case has top-level aggregated failing tests", + "lineage": [ + "Tests::Case", + "has top-level aggregated failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3131000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 13 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:14:in `block (2 levels) in \u003ctop (required)\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:15:in `block (2 levels) in \u003ctop (required)\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:5]", + "name": "Tests::Case has top-level passing pended tests", + "lineage": [ + "Tests::Case", + "has top-level passing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3167000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 23 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/class_spec.rb:23" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:2]", + "name": "Tests::Case behaves like shared examples has top-level failing tests", + "lineage": [ + "Tests::Case behaves like shared examples", + "has top-level failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2971000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 6 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:3]", + "name": "Tests::Case behaves like shared examples has top-level aggregated failing tests", + "lineage": [ + "Tests::Case behaves like shared examples", + "has top-level aggregated failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2540000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 10 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:5]", + "name": "Tests::Case behaves like shared examples has top-level passing pended tests", + "lineage": [ + "Tests::Case behaves like shared examples", + "has top-level passing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3049000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 20 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:20" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:2]", + "name": "Tests::Case behaves like shared examples within a context has failing tests", + "lineage": [ + "Tests::Case behaves like shared examples within a context", + "has failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2979000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 35 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:3]", + "name": "Tests::Case behaves like shared examples within a context has aggregated failing tests", + "lineage": [ + "Tests::Case behaves like shared examples within a context", + "has aggregated failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2438000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 39 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:5]", + "name": "Tests::Case behaves like shared examples within a context has passing pended tests", + "lineage": [ + "Tests::Case behaves like shared examples within a context", + "has passing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2583000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 49 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:49" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:2]", + "name": "Tests::Case within a context has failing tests", + "lineage": [ + "Tests::Case within a context", + "has failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3060000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 40 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/class_spec.rb:41:in `block (3 levels) in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:3]", + "name": "Tests::Case within a context has aggregated failing tests", + "lineage": [ + "Tests::Case within a context", + "has aggregated failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3055000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 44 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:45:in `block (3 levels) in \u003ctop (required)\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:46:in `block (3 levels) in \u003ctop (required)\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:5]", + "name": "Tests::Case within a context has passing pended tests", + "lineage": [ + "Tests::Case within a context", + "has passing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2014000, + "meta": { + "filePath": "./spec/examples/class_spec.rb", + "lineNumber": 54 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/class_spec.rb:54" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:2]", + "name": "Tests::Case within a context behaves like shared examples has top-level failing tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples", + "has top-level failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2811000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 6 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:3]", + "name": "Tests::Case within a context behaves like shared examples has top-level aggregated failing tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples", + "has top-level aggregated failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2654000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 10 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:5]", + "name": "Tests::Case within a context behaves like shared examples has top-level passing pended tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples", + "has top-level passing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3018000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 20 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:20" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:2]", + "name": "Tests::Case within a context behaves like shared examples within a context has failing tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples within a context", + "has failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2861000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 35 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:3]", + "name": "Tests::Case within a context behaves like shared examples within a context has aggregated failing tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples within a context", + "has aggregated failing tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3040000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 39 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:5]", + "name": "Tests::Case within a context behaves like shared examples within a context has passing pended tests", + "lineage": [ + "Tests::Case within a context behaves like shared examples within a context", + "has passing pended tests" + ], + "location": { + "file": "./spec/examples/class_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2672000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 49 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:49" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:2]", + "name": "some string has top-level failing tests", + "lineage": [ + "some string", + "has top-level failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2240000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 8 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/string_spec.rb:9:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:3]", + "name": "some string has top-level aggregated failing tests", + "lineage": [ + "some string", + "has top-level aggregated failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2886000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 12 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:13:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:14:in `block (2 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:5]", + "name": "some string has top-level passing pended tests", + "lineage": [ + "some string", + "has top-level passing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2922000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 22 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/string_spec.rb:22" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:2]", + "name": "some string behaves like shared examples has top-level failing tests", + "lineage": [ + "some string behaves like shared examples", + "has top-level failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2541000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 6 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:3]", + "name": "some string behaves like shared examples has top-level aggregated failing tests", + "lineage": [ + "some string behaves like shared examples", + "has top-level aggregated failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2879000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 10 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:5]", + "name": "some string behaves like shared examples has top-level passing pended tests", + "lineage": [ + "some string behaves like shared examples", + "has top-level passing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2721000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 20 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:20" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:2]", + "name": "some string behaves like shared examples within a context has failing tests", + "lineage": [ + "some string behaves like shared examples within a context", + "has failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2262000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 35 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:3]", + "name": "some string behaves like shared examples within a context has aggregated failing tests", + "lineage": [ + "some string behaves like shared examples within a context", + "has aggregated failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2288000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 39 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:5]", + "name": "some string behaves like shared examples within a context has passing pended tests", + "lineage": [ + "some string behaves like shared examples within a context", + "has passing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2546000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 49 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:49" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:2]", + "name": "some string within a context has failing tests", + "lineage": [ + "some string within a context", + "has failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2598000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 39 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/string_spec.rb:40:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:3]", + "name": "some string within a context has aggregated failing tests", + "lineage": [ + "some string within a context", + "has aggregated failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2331000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 43 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:44:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:45:in `block (3 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:5]", + "name": "some string within a context has passing pended tests", + "lineage": [ + "some string within a context", + "has passing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2118000, + "meta": { + "filePath": "./spec/examples/string_spec.rb", + "lineNumber": 53 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/string_spec.rb:53" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:2]", + "name": "some string within a context behaves like shared examples has top-level failing tests", + "lineage": [ + "some string within a context behaves like shared examples", + "has top-level failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 1989000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 6 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:3]", + "name": "some string within a context behaves like shared examples has top-level aggregated failing tests", + "lineage": [ + "some string within a context behaves like shared examples", + "has top-level aggregated failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2512000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 10 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:5]", + "name": "some string within a context behaves like shared examples has top-level passing pended tests", + "lineage": [ + "some string within a context behaves like shared examples", + "has top-level passing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2402000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 20 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:20" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:2]", + "name": "some string within a context behaves like shared examples within a context has failing tests", + "lineage": [ + "some string within a context behaves like shared examples within a context", + "has failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2212000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 35 + }, + "status": { + "kind": "failed", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "exception": "RSpec::Expectations::ExpectationNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:3]", + "name": "some string within a context behaves like shared examples within a context has aggregated failing tests", + "lineage": [ + "some string within a context behaves like shared examples within a context", + "has aggregated failing tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 3165000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 39 + }, + "status": { + "kind": "failed", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "exception": "RSpec::Expectations::MultipleExpectationsNotMetError", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:5]", + "name": "some string within a context behaves like shared examples within a context has passing pended tests", + "lineage": [ + "some string within a context behaves like shared examples within a context", + "has passing pended tests" + ], + "location": { + "file": "./spec/examples/string_spec.rb" + }, + "attempt": { + "durationInNanoseconds": 2212000, + "meta": { + "filePath": "./spec/examples/shared_examples.rb", + "lineNumber": 49 + }, + "status": { + "kind": "failed", + "message": "Expected example to fail since it is pending, but it passed.", + "exception": "RSpec::Core::Pending::PendingExampleFixedError", + "backtrace": [ + "./spec/examples/shared_examples.rb:49" + ] + } + } + } + ] +} + diff --git a/internal/captain/targetedretries/.snapshots/JavaScriptCucumberSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/JavaScriptCucumberSubstitution works with a real file new file mode 100644 index 0000000..11a5704 --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/JavaScriptCucumberSubstitution works with a real file @@ -0,0 +1,5 @@ +([]map[string]string) (len=1) { + (map[string]string) (len=1) { + (string) (len=9) "scenarios": (string) (len=80) "'features/rule.feature:14' 'features/rule.feature:35' 'features/rule.feature:41'" + } +} diff --git a/internal/captain/targetedretries/.snapshots/JavaScriptCypressSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/JavaScriptCypressSubstitution works with a real file new file mode 100644 index 0000000..6071d9b --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/JavaScriptCypressSubstitution works with a real file @@ -0,0 +1,6 @@ +([]map[string]string) (len=1) { + (map[string]string) (len=2) { + (string) (len=4) "grep": (string) (len=185) "grep='example to-do app one displays two todo items by default; example to-do app one can add new todo items; example to-do app one with a checked task can filter for uncompleted tasks'", + (string) (len=4) "spec": (string) (len=26) "cypress/e2e/todo-one.cy.js" + } +} diff --git a/internal/captain/targetedretries/.snapshots/JavaScriptJestSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/JavaScriptJestSubstitution works with a real file new file mode 100644 index 0000000..cdb8a1f --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/JavaScriptJestSubstitution works with a real file @@ -0,0 +1,14 @@ +([]map[string]string) (len=3) { + (map[string]string) (len=2) { + (string) (len=15) "testNamePattern": (string) (len=74) "^even more nesting is failing|even more nesting is asynchronously failing$", + (string) (len=15) "testPathPattern": (string) (len=87) "/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js" + }, + (map[string]string) (len=2) { + (string) (len=15) "testNamePattern": (string) (len=80) "^one level of nesting is failing|one level of nesting is asynchronously failing$", + (string) (len=15) "testPathPattern": (string) (len=82) "/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js" + }, + (map[string]string) (len=2) { + (string) (len=15) "testNamePattern": (string) (len=58) "^is top-level failing|is asynchronously top-level failing$", + (string) (len=15) "testPathPattern": (string) (len=78) "/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js" + } +} diff --git a/internal/captain/targetedretries/.snapshots/JavaScriptMochaSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/JavaScriptMochaSubstitution works with a real file new file mode 100644 index 0000000..8b60009 --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/JavaScriptMochaSubstitution works with a real file @@ -0,0 +1,10 @@ +([]map[string]string) (len=2) { + (map[string]string) (len=2) { + (string) (len=4) "file": (string) (len=63) "/Users/kylekthompson/src/captain-examples/mocha/test/retries.js", + (string) (len=4) "grep": (string) (len=27) "^is a failing retried test$" + }, + (map[string]string) (len=2) { + (string) (len=4) "file": (string) (len=70) "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + (string) (len=4) "grep": (string) (len=71) "^is a top-level sync failing test|is a top-level sync exceptional test$" + } +} diff --git a/internal/captain/targetedretries/.snapshots/JavaScriptPlaywrightSubstitution works with the example template b/internal/captain/targetedretries/.snapshots/JavaScriptPlaywrightSubstitution works with the example template new file mode 100644 index 0000000..6ea26e1 --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/JavaScriptPlaywrightSubstitution works with the example template @@ -0,0 +1,10 @@ +([]map[string]string) (len=2) { + (map[string]string) (len=2) { + (string) (len=7) "project": (string) (len=8) "chromium", + (string) (len=5) "tests": (string) (len=172) "example.spec.ts:37 example.spec.ts:42 example.spec.ts:47 example.spec.ts:82 example.spec.ts:94 nested/example.spec.ts:37 nested/example.spec.ts:72 nested/example.spec.ts:84" + }, + (map[string]string) (len=2) { + (string) (len=7) "project": (string) (len=7) "firefox", + (string) (len=5) "tests": (string) (len=172) "example.spec.ts:37 example.spec.ts:42 example.spec.ts:47 example.spec.ts:82 example.spec.ts:94 nested/example.spec.ts:37 nested/example.spec.ts:72 nested/example.spec.ts:84" + } +} diff --git a/internal/captain/targetedretries/.snapshots/JavaScriptPlaywrightSubstitution works with the new template format b/internal/captain/targetedretries/.snapshots/JavaScriptPlaywrightSubstitution works with the new template format new file mode 100644 index 0000000..6ea26e1 --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/JavaScriptPlaywrightSubstitution works with the new template format @@ -0,0 +1,10 @@ +([]map[string]string) (len=2) { + (map[string]string) (len=2) { + (string) (len=7) "project": (string) (len=8) "chromium", + (string) (len=5) "tests": (string) (len=172) "example.spec.ts:37 example.spec.ts:42 example.spec.ts:47 example.spec.ts:82 example.spec.ts:94 nested/example.spec.ts:37 nested/example.spec.ts:72 nested/example.spec.ts:84" + }, + (map[string]string) (len=2) { + (string) (len=7) "project": (string) (len=7) "firefox", + (string) (len=5) "tests": (string) (len=172) "example.spec.ts:37 example.spec.ts:42 example.spec.ts:47 example.spec.ts:82 example.spec.ts:94 nested/example.spec.ts:37 nested/example.spec.ts:72 nested/example.spec.ts:84" + } +} diff --git a/internal/captain/targetedretries/.snapshots/JavaScriptPlaywrightSubstitution works with the old template format b/internal/captain/targetedretries/.snapshots/JavaScriptPlaywrightSubstitution works with the old template format new file mode 100644 index 0000000..aebb20f --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/JavaScriptPlaywrightSubstitution works with the old template format @@ -0,0 +1,22 @@ +([]map[string]string) (len=4) { + (map[string]string) (len=3) { + (string) (len=4) "file": (string) (len=15) "example.spec.ts", + (string) (len=4) "grep": (string) (len=79) "failing test|throw error test|throw string|passing w/ fail annotation|times out", + (string) (len=7) "project": (string) (len=8) "chromium" + }, + (map[string]string) (len=3) { + (string) (len=4) "file": (string) (len=15) "example.spec.ts", + (string) (len=4) "grep": (string) (len=79) "failing test|throw error test|throw string|passing w/ fail annotation|times out", + (string) (len=7) "project": (string) (len=7) "firefox" + }, + (map[string]string) (len=3) { + (string) (len=4) "file": (string) (len=22) "nested/example.spec.ts", + (string) (len=4) "grep": (string) (len=49) "failing test|passing w/ fail annotation|times out", + (string) (len=7) "project": (string) (len=8) "chromium" + }, + (map[string]string) (len=3) { + (string) (len=4) "file": (string) (len=22) "nested/example.spec.ts", + (string) (len=4) "grep": (string) (len=49) "failing test|passing w/ fail annotation|times out", + (string) (len=7) "project": (string) (len=7) "firefox" + } +} diff --git a/internal/captain/targetedretries/.snapshots/JavaScriptVitestSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/JavaScriptVitestSubstitution works with a real file new file mode 100644 index 0000000..34c02aa --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/JavaScriptVitestSubstitution works with a real file @@ -0,0 +1,6 @@ +([]map[string]string) (len=1) { + (map[string]string) (len=2) { + (string) (len=4) "file": (string) (len=60) "/Users/kylekthompson/src/captain-examples/vitest/sum.test.js", + (string) (len=15) "testNamePattern": (string) (len=89) "^first level of nesting it fails|first level of nesting second level of nesting it fails$" + } +} diff --git a/internal/captain/targetedretries/.snapshots/PHPUnitSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/PHPUnitSubstitution works with a real file new file mode 100644 index 0000000..89984b8 --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/PHPUnitSubstitution works with a real file @@ -0,0 +1,22 @@ +([]map[string]string) (len=5) { + (map[string]string) (len=2) { + (string) (len=4) "file": (string) (len=40) "/var/www/html/tests/Unit/ExampleTest.php", + (string) (len=6) "filter": (string) (len=31) "test_that_is_a_custom_exception" + }, + (map[string]string) (len=2) { + (string) (len=4) "file": (string) (len=40) "/var/www/html/tests/Unit/ExampleTest.php", + (string) (len=6) "filter": (string) (len=25) "test_that_is_an_exception" + }, + (map[string]string) (len=2) { + (string) (len=4) "file": (string) (len=40) "/var/www/html/tests/Unit/ExampleTest.php", + (string) (len=6) "filter": (string) (len=23) "test_that_true_is_false" + }, + (map[string]string) (len=2) { + (string) (len=4) "file": (string) (len=47) "/var/www/html/tests/Unit/Nested/ExampleTest.php", + (string) (len=6) "filter": (string) (len=17) "test_that_is_slow" + }, + (map[string]string) (len=2) { + (string) (len=4) "file": (string) (len=47) "/var/www/html/tests/Unit/Nested/ExampleTest.php", + (string) (len=6) "filter": (string) (len=23) "test_that_true_is_false" + } +} diff --git a/internal/captain/targetedretries/.snapshots/PythonPytestSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/PythonPytestSubstitution works with a real file new file mode 100644 index 0000000..a765187 --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/PythonPytestSubstitution works with a real file @@ -0,0 +1,5 @@ +([]map[string]string) (len=1) { + (map[string]string) (len=1) { + (string) (len=5) "tests": (string) (len=437) "'nested/test_nested.py::TestSomeClass::test_some_method' 'nested/test_nested.py::TestSomeClassFailingSetup::test_some_method' 'nested/test_nested.py::TestSomeClassFailingTeardown::test_some_method' 'nested/test_nested.py::test_exception' 'nested/test_nested.py::test_nested_failing' 'nested/test_nested.py::test_parameterized[6*9-42]' 'nested/test_nested.py::test_strict_expected_fail_passing' 'test_top_level.py::test_top_level_failing'" + } +} diff --git a/internal/captain/targetedretries/.snapshots/PythonUnitTestSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/PythonUnitTestSubstitution works with a real file new file mode 100644 index 0000000..3cb1aa7 --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/PythonUnitTestSubstitution works with a real file @@ -0,0 +1,5 @@ +([]map[string]string) (len=1) { + (map[string]string) (len=1) { + (string) (len=5) "tests": (string) (len=41) "'test.TestSequenceFunctions.test_failure'" + } +} diff --git a/internal/captain/targetedretries/.snapshots/RubyCucumberSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/RubyCucumberSubstitution works with a real file new file mode 100644 index 0000000..11a5704 --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/RubyCucumberSubstitution works with a real file @@ -0,0 +1,5 @@ +([]map[string]string) (len=1) { + (map[string]string) (len=1) { + (string) (len=9) "scenarios": (string) (len=80) "'features/rule.feature:14' 'features/rule.feature:35' 'features/rule.feature:41'" + } +} diff --git a/internal/captain/targetedretries/.snapshots/RubyMinitestSubstitution works with a real file for Rails b/internal/captain/targetedretries/.snapshots/RubyMinitestSubstitution works with a real file for Rails new file mode 100644 index 0000000..020378d --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/RubyMinitestSubstitution works with a real file for Rails @@ -0,0 +1,5 @@ +([]map[string]string) (len=1) { + (map[string]string) (len=1) { + (string) (len=5) "tests": (string) (len=101) "'test/failing_test.rb:4' 'test/failing_test.rb:8' 'test/failing_test.rb:12' 'test/failing_test.rb:25'" + } +} diff --git a/internal/captain/targetedretries/.snapshots/RubyMinitestSubstitution works with a real file for non-Rails b/internal/captain/targetedretries/.snapshots/RubyMinitestSubstitution works with a real file for non-Rails new file mode 100644 index 0000000..40320be --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/RubyMinitestSubstitution works with a real file for non-Rails @@ -0,0 +1,18 @@ +([]map[string]string) (len=4) { + (map[string]string) (len=2) { + (string) (len=4) "file": (string) (len=20) "test/failing_test.rb", + (string) (len=4) "name": (string) (len=23) "test_assert_equal_fails" + }, + (map[string]string) (len=2) { + (string) (len=4) "file": (string) (len=20) "test/failing_test.rb", + (string) (len=4) "name": (string) (len=10) "test_fails" + }, + (map[string]string) (len=2) { + (string) (len=4) "file": (string) (len=20) "test/failing_test.rb", + (string) (len=4) "name": (string) (len=11) "test_raises" + }, + (map[string]string) (len=2) { + (string) (len=4) "file": (string) (len=20) "test/failing_test.rb", + (string) (len=4) "name": (string) (len=18) "test_raises_custom" + } +} diff --git a/internal/captain/targetedretries/.snapshots/RubyRSpecSubstitution works with a real file b/internal/captain/targetedretries/.snapshots/RubyRSpecSubstitution works with a real file new file mode 100644 index 0000000..fe1d16c --- /dev/null +++ b/internal/captain/targetedretries/.snapshots/RubyRSpecSubstitution works with a real file @@ -0,0 +1,5 @@ +([]map[string]string) (len=1) { + (map[string]string) (len=1) { + (string) (len=5) "tests": (string) (len=1457) "'./spec/examples/class_spec.rb[1:2]' './spec/examples/class_spec.rb[1:3]' './spec/examples/class_spec.rb[1:5]' './spec/examples/class_spec.rb[1:7:2]' './spec/examples/class_spec.rb[1:7:3]' './spec/examples/class_spec.rb[1:7:5]' './spec/examples/class_spec.rb[1:7:7:2]' './spec/examples/class_spec.rb[1:7:7:3]' './spec/examples/class_spec.rb[1:7:7:5]' './spec/examples/class_spec.rb[1:8:2]' './spec/examples/class_spec.rb[1:8:3]' './spec/examples/class_spec.rb[1:8:5]' './spec/examples/class_spec.rb[1:8:7:2]' './spec/examples/class_spec.rb[1:8:7:3]' './spec/examples/class_spec.rb[1:8:7:5]' './spec/examples/class_spec.rb[1:8:7:7:2]' './spec/examples/class_spec.rb[1:8:7:7:3]' './spec/examples/class_spec.rb[1:8:7:7:5]' './spec/examples/string_spec.rb[1:2]' './spec/examples/string_spec.rb[1:3]' './spec/examples/string_spec.rb[1:5]' './spec/examples/string_spec.rb[1:7:2]' './spec/examples/string_spec.rb[1:7:3]' './spec/examples/string_spec.rb[1:7:5]' './spec/examples/string_spec.rb[1:7:7:2]' './spec/examples/string_spec.rb[1:7:7:3]' './spec/examples/string_spec.rb[1:7:7:5]' './spec/examples/string_spec.rb[1:8:2]' './spec/examples/string_spec.rb[1:8:3]' './spec/examples/string_spec.rb[1:8:5]' './spec/examples/string_spec.rb[1:8:7:2]' './spec/examples/string_spec.rb[1:8:7:3]' './spec/examples/string_spec.rb[1:8:7:5]' './spec/examples/string_spec.rb[1:8:7:7:2]' './spec/examples/string_spec.rb[1:8:7:7:3]' './spec/examples/string_spec.rb[1:8:7:7:5]'" + } +} diff --git a/internal/captain/targetedretries/dot_net_xunit_substitution.go b/internal/captain/targetedretries/dot_net_xunit_substitution.go new file mode 100644 index 0000000..01a373b --- /dev/null +++ b/internal/captain/targetedretries/dot_net_xunit_substitution.go @@ -0,0 +1,91 @@ +package targetedretries + +import ( + "fmt" + "regexp" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type DotNetxUnitSubstitution struct{} + +func (s DotNetxUnitSubstitution) Example() string { + return "dotnet test --filter '{{ filter }}'" +} + +func (s DotNetxUnitSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError("Retrying xUnit requires a template with the 'filter' keyword; no keywords were found") + } + + if len(keywords) > 1 { + return errors.NewInputError( + "Retrying xUnit requires a template with only the 'filter' keyword; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if keywords[0] != "filter" { + return errors.NewInputError( + "Retrying xUnit requires a template with only the 'filter' keyword; '%v' was found instead", + keywords[0], + ) + } + + return nil +} + +// https://github.com/microsoft/vstest/blob/main/docs/filter.md +var testFilterSpecialCharacters = regexp.MustCompile(`[()\\&|=!~]`) + +func escapeTestFilterCharacter(value string) string { + return fmt.Sprintf(`\%v`, value) +} + +func (s DotNetxUnitSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + testsSeen := map[string]struct{}{} + tests := make([]string, 0) + + for _, test := range testResults.Tests { + if !filter(test) { + continue + } + + testType, ok := test.Attempt.Meta["type"].(string) + if !ok { + return nil, errors.NewInternalError("Expected 'type' in meta to be string, got %T", test.Attempt.Meta["type"]) + } + + testMethod, ok := test.Attempt.Meta["method"].(string) + if !ok { + return nil, errors.NewInternalError("Expected 'method' in meta to be string, got %T", test.Attempt.Meta["method"]) + } + + fullyQualifiedName := testFilterSpecialCharacters.ReplaceAllStringFunc( + fmt.Sprintf("%v.%v", testType, testMethod), + escapeTestFilterCharacter, + ) + formattedTest := templating.ShellEscape(fmt.Sprintf("FullyQualifiedName=%v", fullyQualifiedName)) + if _, ok := testsSeen[formattedTest]; ok { + continue + } + + tests = append(tests, formattedTest) + testsSeen[formattedTest] = struct{}{} + } + + if len(tests) > 0 { + return []map[string]string{{"filter": strings.Join(tests, " | ")}}, nil + } + + return []map[string]string{}, nil +} diff --git a/internal/captain/targetedretries/dot_net_xunit_substitution_test.go b/internal/captain/targetedretries/dot_net_xunit_substitution_test.go new file mode 100644 index 0000000..571a07a --- /dev/null +++ b/internal/captain/targetedretries/dot_net_xunit_substitution_test.go @@ -0,0 +1,296 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("DotNetxUnitSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.DotNetxUnitSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.DotNetxUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/xunit_dot_net.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.DotNetxUnitParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["filter"] < substitutions[j]["filter"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.DotNetxUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.DotNetxUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("dotnet test --filter") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too many placeholders", func() { + substitution := targetedretries.DotNetxUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("dotnet test --filter '{{ filter }}' {{ other }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template without a filter placeholder", func() { + substitution := targetedretries.DotNetxUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("dotnet test --filter '{{ other }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with only a filter placeholder", func() { + substitution := targetedretries.DotNetxUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("dotnet test --filter '{{ filter }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns the unique test type.method", func() { + compiledTemplate, compileErr := templating.CompileTemplate("dotnet test --filter '{{ filter }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type1", "method": "method1"}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type2", "method": "method2"}, + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type3", "method": "method3"}, + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type3", "method": "method3"}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type4", "method": "method4"}, + Status: v1.NewPendedTestStatus(nil), + }, + }, + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type5", "method": "method5"}, + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type6", "method": "method6"}, + Status: v1.NewSkippedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.DotNetxUnitSubstitution{} + Expect(substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + )).To(Equal( + []map[string]string{ + { + "filter": "FullyQualifiedName=type1.method1 | " + + "FullyQualifiedName=type2.method2 | FullyQualifiedName=type3.method3", + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate("dotnet test --filter '{{ filter }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type1", "method": "method1"}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type2", "method": "method2"}, + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type3", "method": "method3"}, + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type3", "method": "method3"}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type4", "method": "method4"}, + Status: v1.NewPendedTestStatus(nil), + }, + }, + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type5", "method": "method5"}, + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type6", "method": "method6"}, + Status: v1.NewSkippedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.DotNetxUnitSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "filter": "FullyQualifiedName=type1.method1 | FullyQualifiedName=type3.method3", + }, + }, + )) + }) + + It("correctly escapes the filter substitution", func() { + compiledTemplate, compileErr := templating.CompileTemplate("dotnet test --filter '{{ filter }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type1", "method": `method1(val1: 100, val2: "test")`}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": "type2", "method": `!method2=|&\`}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + }, + } + + substitution := targetedretries.DotNetxUnitSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "filter": `FullyQualifiedName=type1.method1\(val1: 100, val2: "test"\) | ` + + `FullyQualifiedName=type2.\!method2\=\|\&\\`, + }, + }, + )) + }) + + It("returns an error when Meta type or method is not a string", func() { + compiledTemplate, compileErr := templating.CompileTemplate("dotnet test --filter '{{ filter }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Attempt: v1.TestAttempt{ + Meta: map[string]any{"type": 123, "method": "method1"}, // type is int, not string + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + }, + } + + substitution := targetedretries.DotNetxUnitSubstitution{} + _, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Expected 'type' in meta to be string")) + }) + }) +}) diff --git a/internal/captain/targetedretries/elixir_exunit_substitution.go b/internal/captain/targetedretries/elixir_exunit_substitution.go new file mode 100644 index 0000000..b9a19bf --- /dev/null +++ b/internal/captain/targetedretries/elixir_exunit_substitution.go @@ -0,0 +1,73 @@ +package targetedretries + +import ( + "fmt" + "strconv" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type ElixirExUnitSubstitution struct{} + +func (s ElixirExUnitSubstitution) Example() string { + return "mix test {{ tests }}" +} + +func (s ElixirExUnitSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError("Retrying ExUnit requires a template with the 'tests' keyword; no keywords were found") + } + + if len(keywords) > 1 { + return errors.NewInputError( + "Retrying ExUnit requires a template with only the 'tests' keyword; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if keywords[0] != "tests" { + return errors.NewInputError( + "Retrying ExUnit requires a template with only the 'tests' keyword; '%v' was found instead", + keywords[0], + ) + } + + return nil +} + +func (s ElixirExUnitSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + testLinesByFile := map[string][]string{} + + for _, test := range testResults.Tests { + if !filter(test) { + continue + } + + file := templating.ShellEscape(test.Location.File) + if _, ok := testLinesByFile[file]; !ok { + testLinesByFile[file] = make([]string, 0) + } + + testLinesByFile[file] = append(testLinesByFile[file], strconv.Itoa(*test.Location.Line)) + } + + substitutions := make([]map[string]string, len(testLinesByFile)) + i := 0 + for file, testLines := range testLinesByFile { + substitutions[i] = map[string]string{ + "tests": fmt.Sprintf("'%v:%v'", file, strings.Join(testLines, ":")), + } + i++ + } + + return substitutions, nil +} diff --git a/internal/captain/targetedretries/elixir_exunit_substitution_test.go b/internal/captain/targetedretries/elixir_exunit_substitution_test.go new file mode 100644 index 0000000..e91350b --- /dev/null +++ b/internal/captain/targetedretries/elixir_exunit_substitution_test.go @@ -0,0 +1,221 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("ElixirExUnitSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.ElixirExUnitSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.ElixirExUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/exunit.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.ElixirExUnitParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["tests"] < substitutions[j]["tests"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.ElixirExUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.ElixirExUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("mix test") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too many placeholders", func() { + substitution := targetedretries.ElixirExUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("mix test {{ file }} {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template without a tests placeholder", func() { + substitution := targetedretries.ElixirExUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("mix test {{ file }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with only a tests placeholder", func() { + substitution := targetedretries.ElixirExUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("mix test {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns the shell escaped test identifiers", func() { + compiledTemplate, compileErr := templating.CompileTemplate("mix test {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "/path/to/file with spaces.rb" + file2 := "/path/to/filewith'.rb" + file3 := "/path/to/file.rb" + + line1 := 10 + line2 := 20 + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Location: &v1.Location{File: file1, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Location: &v1.Location{File: file2, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Location: &v1.Location{File: file1, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Location: &v1.Location{File: file3, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Location: &v1.Location{File: file2, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Location: &v1.Location{File: file3, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.ElixirExUnitSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["tests"] < substitutions[j]["tests"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "tests": "'/path/to/file with spaces.rb:10:20'", + }, + { + "tests": `'/path/to/filewith'"'"'.rb:10'`, + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate("mix test {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "/path/to/file with spaces.rb" + file2 := "/path/to/filewith'.rb" + file3 := "/path/to/file.rb" + + line1 := 10 + line2 := 20 + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Location: &v1.Location{File: file1, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Location: &v1.Location{File: file2, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Location: &v1.Location{File: file1, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Location: &v1.Location{File: file3, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Location: &v1.Location{File: file2, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Location: &v1.Location{File: file3, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.ElixirExUnitSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["tests"] < substitutions[j]["tests"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "tests": "'/path/to/file with spaces.rb:10'", + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/go_ginkgo_substitution.go b/internal/captain/targetedretries/go_ginkgo_substitution.go new file mode 100644 index 0000000..997792b --- /dev/null +++ b/internal/captain/targetedretries/go_ginkgo_substitution.go @@ -0,0 +1,63 @@ +package targetedretries + +import ( + "fmt" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type GoGinkgoSubstitution struct{} + +func (s GoGinkgoSubstitution) Example() string { + return "ginkgo run {{ tests }} ./..." +} + +func (s GoGinkgoSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError("Retrying Ginkgo requires a template with the 'tests' keyword; no keywords were found") + } + + if len(keywords) > 1 { + return errors.NewInputError( + "Retrying Ginkgo requires a template with only the 'tests' keyword; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if keywords[0] != "tests" { + return errors.NewInputError( + "Retrying Ginkgo requires a template with only the 'tests' keyword; '%v' was found instead", + keywords[0], + ) + } + + return nil +} + +func (s GoGinkgoSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + formattedTests := make([]string, 0) + + for _, test := range testResults.Tests { + if filter(test) { + formattedTests = append( + formattedTests, + fmt.Sprintf("--focus-file '%v:%v'", templating.ShellEscape(test.Location.File), *test.Location.Line), + ) + } + } + + if len(formattedTests) > 0 { + return []map[string]string{{"tests": strings.Join(formattedTests, " ")}}, nil + } + + return []map[string]string{}, nil +} diff --git a/internal/captain/targetedretries/go_ginkgo_substitution_test.go b/internal/captain/targetedretries/go_ginkgo_substitution_test.go new file mode 100644 index 0000000..6f8c562 --- /dev/null +++ b/internal/captain/targetedretries/go_ginkgo_substitution_test.go @@ -0,0 +1,210 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GoGinkgoSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.GoGinkgoSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.GoGinkgoSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/ginkgo.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.GoGinkgoParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["tests"] < substitutions[j]["tests"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.GoGinkgoSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.GoGinkgoSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("ginkgo run ./...") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too many placeholders", func() { + substitution := targetedretries.GoGinkgoSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("ginkgo run {{ file }} {{ tests }} ./...") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template without a tests placeholder", func() { + substitution := targetedretries.GoGinkgoSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("ginkgo run {{ file }} ./...") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with only a tests placeholder", func() { + substitution := targetedretries.GoGinkgoSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("ginkgo run {{ tests }} ./...") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns the shell escaped test locations", func() { + compiledTemplate, compileErr := templating.CompileTemplate("ginkgo run {{ tests }} ./...") + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "/path/to/file with spaces.go" + file2 := "/path/to/filewith'.go" + file3 := "/path/to/otherfile.go" + line1 := 1 + line2 := 100 + line3 := 1249 + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Location: &v1.Location{File: file1, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Location: &v1.Location{File: file2, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Location: &v1.Location{File: file3, Line: &line3}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Location: &v1.Location{File: file2, Line: &line3}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Location: &v1.Location{File: file1, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Location: &v1.Location{File: file3, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.GoGinkgoSubstitution{} + Expect(substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + )).To(Equal( + []map[string]string{ + { + "tests": `--focus-file '/path/to/file with spaces.go:1' ` + + `--focus-file '/path/to/filewith'"'"'.go:100' ` + + `--focus-file '/path/to/otherfile.go:1249'`, + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate("ginkgo run {{ tests }} ./...") + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "/path/to/file with spaces.go" + file2 := "/path/to/filewith'.go" + file3 := "/path/to/otherfile.go" + line1 := 1 + line2 := 100 + line3 := 1249 + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Location: &v1.Location{File: file1, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Location: &v1.Location{File: file2, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Location: &v1.Location{File: file3, Line: &line3}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Location: &v1.Location{File: file2, Line: &line3}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Location: &v1.Location{File: file1, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Location: &v1.Location{File: file3, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.GoGinkgoSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "tests": "--focus-file '/path/to/file with spaces.go:1'", + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/go_test_substitution.go b/internal/captain/targetedretries/go_test_substitution.go new file mode 100644 index 0000000..a77f884 --- /dev/null +++ b/internal/captain/targetedretries/go_test_substitution.go @@ -0,0 +1,92 @@ +package targetedretries + +import ( + "fmt" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type GoTestSubstitution struct{} + +func (s GoTestSubstitution) Example() string { + return "go test '{{ package }}' -run '{{ run }}'" +} + +func (s GoTestSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError( + "Retrying go test requires a template with the 'package' and 'run' keywords; no keywords were found", + ) + } + + if len(keywords) != 2 { + return errors.NewInputError( + "Retrying go test requires a template with the 'package' and 'run' keywords; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if (keywords[0] == "package" && keywords[1] == "run") || (keywords[0] == "run" && keywords[1] == "package") { + return nil + } + + return errors.NewInputError( + "Retrying go test requires a template with the 'package' and 'run' keywords; '%v' and '%v' were found instead", + keywords[0], + keywords[1], + ) +} + +func (s GoTestSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + testsByPackage := map[string][]string{} + testsSeenByPackage := map[string]map[string]struct{}{} + + for _, test := range testResults.Tests { + if !filter(test) { + continue + } + + testPackage := templating.ShellEscape(test.Attempt.Meta["package"].(string)) + testName := test.Name + if strings.Contains(testName, "/") { + // Table tests cannot be run individually, so run them all + testName = strings.Split(testName, "/")[0] + } + + if _, ok := testsSeenByPackage[testPackage]; !ok { + testsSeenByPackage[testPackage] = map[string]struct{}{} + } + if _, ok := testsByPackage[testPackage]; !ok { + testsByPackage[testPackage] = make([]string, 0) + } + + formattedTest := templating.RegexpEscape(testName) + if _, ok := testsSeenByPackage[testPackage][formattedTest]; ok { + continue + } + + testsByPackage[testPackage] = append(testsByPackage[testPackage], formattedTest) + testsSeenByPackage[testPackage][formattedTest] = struct{}{} + } + + substitutions := make([]map[string]string, len(testsByPackage)) + i := 0 + for testPackage, tests := range testsByPackage { + substitutions[i] = map[string]string{ + "package": testPackage, + "run": fmt.Sprintf("^%v$", strings.Join(tests, "|")), + } + i++ + } + + return substitutions, nil +} diff --git a/internal/captain/targetedretries/go_test_substitution_test.go b/internal/captain/targetedretries/go_test_substitution_test.go new file mode 100644 index 0000000..599ddd9 --- /dev/null +++ b/internal/captain/targetedretries/go_test_substitution_test.go @@ -0,0 +1,357 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GoTestSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.GoTestSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.GoTestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/go_test.jsonl") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.GoTestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + if substitutions[i]["package"] != substitutions[j]["package"] { + return substitutions[i]["package"] < substitutions[j]["package"] + } + + return substitutions[i]["run"] < substitutions[j]["run"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.GoTestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.GoTestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("go test") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too few placeholders", func() { + substitution := targetedretries.GoTestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("go test '{{ package }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with additional placeholders", func() { + substitution := targetedretries.GoTestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("go test '{{ package }}' -run '{{ run }}' {{ foo }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with incorrect placeholders", func() { + substitution := targetedretries.GoTestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("go test '{{ what }}' -run '{{ other }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with the package and run placeholders", func() { + substitution := targetedretries.GoTestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("go test '{{ package }}' -run '{{ run }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns tests grouped by package", func() { + compiledTemplate, compileErr := templating.CompileTemplate("go test '{{ package }}' -run '{{ run }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + package1 := "package1" + package2 := "package2" + + name1 := "name1" + name2 := "name2" + name3 := "name3" + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Name: name1, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package1}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Name: name1, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package2}, + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Name: name2, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package1}, + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Name: name2, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package2}, + Status: v1.NewPendedTestStatus(nil), + }, + }, + { + Name: name3, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package1}, + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Name: name3, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package2}, + Status: v1.NewSkippedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.GoTestSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["package"] < substitutions[j]["package"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "package": "package1", + "run": "^name1|name2$", + }, + { + "package": "package2", + "run": "^name1$", + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate("go test '{{ package }}' -run '{{ run }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + package1 := "package1" + package2 := "package2" + + name1 := "name1" + name2 := "name2" + name3 := "name3" + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Name: name1, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package1}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Name: name1, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package2}, + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Name: name2, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package1}, + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Name: name2, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package2}, + Status: v1.NewPendedTestStatus(nil), + }, + }, + { + Name: name3, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package1}, + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Name: name3, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package2}, + Status: v1.NewSkippedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.GoTestSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["package"] < substitutions[j]["package"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "package": "package1", + "run": "^name1$", + }, + }, + )) + }) + + It("regex escapes the test name and shell escapes the package", func() { + compiledTemplate, compileErr := templating.CompileTemplate("go test '{{ package }}' -run '{{ run }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + package1 := "package1 with ' embedded" + name1 := "name1 with + and . embedded" + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Name: name1, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package1}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + }, + } + + substitution := targetedretries.GoTestSubstitution{} + Expect(substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(_ v1.Test) bool { return true }, + )).To(Equal( + []map[string]string{ + { + "package": `package1 with '"'"' embedded`, + "run": `^name1 with \+ and \. embedded$`, + }, + }, + )) + }) + + It("adds entire table tests one time when it sees them", func() { + compiledTemplate, compileErr := templating.CompileTemplate("go test '{{ package }}' -run '{{ run }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + package1 := "package1" + table1 := "Table/1" + table2 := "Table/2" + table3 := "Table/3" + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Name: table1, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package1}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Name: table2, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package1}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Name: table3, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"package": package1}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + }, + } + + substitution := targetedretries.GoTestSubstitution{} + Expect(substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(_ v1.Test) bool { return true }, + )).To(Equal( + []map[string]string{ + { + "package": "package1", + "run": "^Table$", + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/javascript_bun_substitution.go b/internal/captain/targetedretries/javascript_bun_substitution.go new file mode 100644 index 0000000..808b218 --- /dev/null +++ b/internal/captain/targetedretries/javascript_bun_substitution.go @@ -0,0 +1,90 @@ +package targetedretries + +import ( + "fmt" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JavaScriptBunSubstitution struct{} + +func (s JavaScriptBunSubstitution) Example() string { + return "bun test '{{ file }}' --test-name-pattern '{{ testNamePattern }}'" +} + +func (s JavaScriptBunSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError( + "Retrying Bun requires a template with the 'file' and 'testNamePattern' keywords; " + + "no keywords were found", + ) + } + + if len(keywords) != 2 { + return errors.NewInputError( + "Retrying Bun requires a template with the 'file' and 'testNamePattern' keywords; "+ + "these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if (keywords[0] == "file" && keywords[1] == "testNamePattern") || + (keywords[0] == "testNamePattern" && keywords[1] == "file") { + return nil + } + + return errors.NewInputError( + "Retrying Bun requires a template with the 'file' and 'testNamePattern' keywords; "+ + "'%v' and '%v' were found instead", + keywords[0], + keywords[1], + ) +} + +func (s JavaScriptBunSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + testsByFile := map[string][]string{} + testsSeenByFile := map[string]map[string]struct{}{} + + for _, test := range testResults.Tests { + if !filter(test) { + continue + } + + file := templating.ShellEscape(test.Location.File) + if _, ok := testsSeenByFile[file]; !ok { + testsSeenByFile[file] = map[string]struct{}{} + } + if _, ok := testsByFile[file]; !ok { + testsByFile[file] = make([]string, 0) + } + + formattedName := templating.ShellEscape(templating.RegexpEscape(strings.Join(test.Lineage, " "))) + if _, ok := testsSeenByFile[file][formattedName]; ok { + continue + } + + testsByFile[file] = append(testsByFile[file], formattedName) + testsSeenByFile[file][formattedName] = struct{}{} + } + + substitutions := make([]map[string]string, len(testsByFile)) + i := 0 + for file, tests := range testsByFile { + substitutions[i] = map[string]string{ + "file": file, + "testNamePattern": fmt.Sprintf("^%v$", strings.Join(tests, "|")), + } + i++ + } + + return substitutions, nil +} diff --git a/internal/captain/targetedretries/javascript_bun_substitution_test.go b/internal/captain/targetedretries/javascript_bun_substitution_test.go new file mode 100644 index 0000000..16d7351 --- /dev/null +++ b/internal/captain/targetedretries/javascript_bun_substitution_test.go @@ -0,0 +1,227 @@ +package targetedretries_test + +import ( + "sort" + + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptBunSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.JavaScriptBunSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.JavaScriptBunSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.JavaScriptBunSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bun test") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too few placeholders", func() { + substitution := targetedretries.JavaScriptBunSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "bun test '{{ file }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with additional placeholders", func() { + substitution := targetedretries.JavaScriptBunSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "bun test '{{ file }}' --test-name-pattern '{{ testNamePattern }}' {{ foo }}", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with incorrect placeholders", func() { + substitution := targetedretries.JavaScriptBunSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "bun test '{{ wat }}' --test-name-pattern '{{ foo }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with the file and testNamePattern placeholders", func() { + substitution := targetedretries.JavaScriptBunSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "bun test '{{ file }}' --test-name-pattern '{{ testNamePattern }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns tests grouped by file", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "bun test '{{ file }}' --test-name-pattern '{{ testNamePattern }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.js" + file2 := "path/to/file with '.js" + + lineage1 := []string{"name of describe", "test 'one' + 1"} + lineage2 := []string{"name of describe", "test 2"} + lineage3 := []string{"name of describe", "test 3"} + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Lineage: lineage1, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage1, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Lineage: lineage3, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Lineage: lineage3, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.JavaScriptBunSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["file"] < substitutions[j]["file"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "file": `path/to/file with '"'"'.js`, + "testNamePattern": `^name of describe test '"'"'one'"'"' \+ 1$`, + }, + { + "file": "path/to/file1.js", + "testNamePattern": `^name of describe test '"'"'one'"'"' \+ 1|name of describe test 2$`, + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "bun test '{{ file }}' --test-name-pattern '{{ testNamePattern }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.js" + file2 := "path/to/file with '.js" + + lineage1 := []string{"name of describe", "test 'one' + 1"} + lineage2 := []string{"name of describe", "test 2"} + lineage3 := []string{"name of describe", "test 3"} + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Lineage: lineage1, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage1, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Lineage: lineage3, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Lineage: lineage3, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.JavaScriptBunSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["file"] < substitutions[j]["file"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "file": "path/to/file1.js", + "testNamePattern": `^name of describe test '"'"'one'"'"' \+ 1$`, + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/javascript_cucumber_substitution.go b/internal/captain/targetedretries/javascript_cucumber_substitution.go new file mode 100644 index 0000000..8a279d7 --- /dev/null +++ b/internal/captain/targetedretries/javascript_cucumber_substitution.go @@ -0,0 +1,73 @@ +package targetedretries + +import ( + "fmt" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JavaScriptCucumberSubstitution struct{} + +func (s JavaScriptCucumberSubstitution) Example() string { + return "npx cucumber-js {{ scenarios }}" +} + +func (s JavaScriptCucumberSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError( + "Retrying Cucumber requires a template with the 'scenarios' keyword; no keywords were found", + ) + } + + if len(keywords) > 1 { + return errors.NewInputError( + "Retrying Cucumber requires a template with only the 'scenarios' keyword; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if keywords[0] != "scenarios" { + return errors.NewInputError( + "Retrying Cucumber requires a template with only the 'scenarios' keyword; '%v' was found instead", + keywords[0], + ) + } + + return nil +} + +func (s JavaScriptCucumberSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + scenarios := make([]string, 0) + scenariosSeen := map[string]struct{}{} + + for _, test := range testResults.Tests { + if filter(test) { + scenarioLocation := test.Location.File + if test.Location.Line != nil { + scenarioLocation = fmt.Sprintf("%s:%v", test.Location.File, *test.Location.Line) + } + example := templating.ShellEscape(scenarioLocation) + if _, ok := scenariosSeen[example]; ok { + continue + } + + scenarios = append(scenarios, fmt.Sprintf("'%v'", example)) + scenariosSeen[example] = struct{}{} + } + } + + if len(scenarios) > 0 { + return []map[string]string{{"scenarios": strings.Join(scenarios, " ")}}, nil + } + + return []map[string]string{}, nil +} diff --git a/internal/captain/targetedretries/javascript_cucumber_substitution_test.go b/internal/captain/targetedretries/javascript_cucumber_substitution_test.go new file mode 100644 index 0000000..56f07fa --- /dev/null +++ b/internal/captain/targetedretries/javascript_cucumber_substitution_test.go @@ -0,0 +1,234 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptCucumberSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.JavaScriptCucumberSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.JavaScriptCucumberSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/cucumber-js.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptCucumberJSONParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["scenarios"] < substitutions[j]["scenarios"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.JavaScriptCucumberSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.JavaScriptCucumberSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("npx cucumber-js") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too many placeholders", func() { + substitution := targetedretries.JavaScriptCucumberSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("npx cucumber-js {{ file }} {{ scenarios }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template without a scenarios placeholder", func() { + substitution := targetedretries.JavaScriptCucumberSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("npx cucumber-js {{ file }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with only a scenarios placeholder", func() { + substitution := targetedretries.JavaScriptCucumberSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("npx cucumber-js {{ scenarios }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns the shell escaped element start identifiers", func() { + compiledTemplate, compileErr := templating.CompileTemplate("npx cucumber-js {{ scenarios }}") + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "file1 with ' single quotes" + file2 := "file2" + file3 := "file3" + file4 := "file4" + file5 := "file5" + file6 := "file6" + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Location: &v1.Location{File: file3}, + Attempt: v1.TestAttempt{ + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Location: &v1.Location{File: file4}, + Attempt: v1.TestAttempt{ + Status: v1.NewPendedTestStatus(nil), + }, + }, + { + Location: &v1.Location{File: file5}, + Attempt: v1.TestAttempt{ + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Location: &v1.Location{File: file6}, + Attempt: v1.TestAttempt{ + Status: v1.NewSkippedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.JavaScriptCucumberSubstitution{} + Expect(substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + )).To(Equal( + []map[string]string{ + { + "scenarios": `'file1 with '"'"' single quotes' ` + + `'file2' ` + + `'file3'`, + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate("npx cucumber-js {{ scenarios }}") + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "file1 with ' single quotes" + file2 := "file2" + file3 := "file3" + file4 := "file4" + file5 := "file5" + file6 := "file6" + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Location: &v1.Location{File: file3}, + Attempt: v1.TestAttempt{ + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Location: &v1.Location{File: file4}, + Attempt: v1.TestAttempt{ + Status: v1.NewPendedTestStatus(nil), + }, + }, + { + Location: &v1.Location{File: file5}, + Attempt: v1.TestAttempt{ + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Location: &v1.Location{File: file6}, + Attempt: v1.TestAttempt{ + Status: v1.NewSkippedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.JavaScriptCucumberSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "scenarios": `'file1 with '"'"' single quotes'`, + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/javascript_cypress_substitution.go b/internal/captain/targetedretries/javascript_cypress_substitution.go new file mode 100644 index 0000000..345cd7b --- /dev/null +++ b/internal/captain/targetedretries/javascript_cypress_substitution.go @@ -0,0 +1,100 @@ +package targetedretries + +import ( + "fmt" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JavaScriptCypressSubstitution struct{} + +func (s JavaScriptCypressSubstitution) Example() string { + return "npx cypress run --spec '{{ spec }}' --env {{ grep }}" +} + +func (s JavaScriptCypressSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError( + "Retrying Cypress requires a template with the 'spec' and optionally the 'grep' keyword; no keywords were found", + ) + } + + if len(keywords) > 2 { + return errors.NewInputError( + "Retrying Cypress requires a template with the 'spec' and optionally the 'grep' keyword; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if len(keywords) == 1 { + keyword := keywords[0] + + if keyword != "spec" { + return errors.NewInputError( + "Retrying Cypress requires a template with the 'spec' and optionally the 'grep' keyword; only '%v' was found", + keyword, + ) + } + + return nil + } + + firstKeyword := keywords[0] + secondKeyword := keywords[1] + if (firstKeyword == "spec" && secondKeyword == "grep") || (firstKeyword == "grep" && secondKeyword == "spec") { + return nil + } + + return errors.NewInputError( + "Retrying Cypress requires a template with the 'spec' and optionally the 'grep' keyword; these were found: %v", + strings.Join(keywords, ", "), + ) +} + +func (s JavaScriptCypressSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + testsBySpec := map[string][]string{} + testsSeenBySpec := map[string]map[string]struct{}{} + + for _, test := range testResults.Tests { + if !filter(test) { + continue + } + + spec := templating.ShellEscape(test.Location.File) + if _, ok := testsSeenBySpec[spec]; !ok { + testsSeenBySpec[spec] = map[string]struct{}{} + } + if _, ok := testsBySpec[spec]; !ok { + testsBySpec[spec] = make([]string, 0) + } + + name := templating.ShellEscape(test.Name) + if _, ok := testsSeenBySpec[spec][name]; ok { + continue + } + + testsBySpec[spec] = append(testsBySpec[spec], name) + testsSeenBySpec[spec][name] = struct{}{} + } + + substitutions := make([]map[string]string, len(testsBySpec)) + i := 0 + for spec, tests := range testsBySpec { + substitutions[i] = map[string]string{ + "spec": spec, + "grep": fmt.Sprintf("grep='%v'", strings.Join(tests, "; ")), + } + i++ + } + + return substitutions, nil +} diff --git a/internal/captain/targetedretries/javascript_cypress_substitution_test.go b/internal/captain/targetedretries/javascript_cypress_substitution_test.go new file mode 100644 index 0000000..3a55641 --- /dev/null +++ b/internal/captain/targetedretries/javascript_cypress_substitution_test.go @@ -0,0 +1,272 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptCypressSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.JavaScriptCypressSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.JavaScriptCypressSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/cypress.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptCypressParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + if substitutions[i]["spec"] != substitutions[j]["spec"] { + return substitutions[i]["spec"] < substitutions[j]["spec"] + } + + return substitutions[i]["grep"] < substitutions[j]["grep"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.JavaScriptCypressSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.JavaScriptCypressSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("npx cypress run") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with additional placeholders", func() { + substitution := targetedretries.JavaScriptCypressSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx cypress run --spec '{{ spec }}' --env {{ grep }} {{ foo }}", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with incorrect placeholders", func() { + substitution := targetedretries.JavaScriptCypressSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx cypress run --spec '{{ wat }}' --env {{ foo }}", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template without the spec placeholder", func() { + substitution := targetedretries.JavaScriptCypressSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx cypress run --env {{ grep }}", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with only the spec placeholder", func() { + substitution := targetedretries.JavaScriptCypressSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx cypress run --spec '{{ spec }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is valid for a template with the spec and grep placeholders", func() { + substitution := targetedretries.JavaScriptCypressSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx cypress run --spec '{{ spec }}' --env {{ grep }}", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns tests grouped by spec", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "npx cypress run --spec '{{ spec }}' --env {{ grep }}", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + spec1 := "spec1.js" + spec2 := "path/to/spec with ' in it.js" + + name1 := "name1" + name2 := "name2 with ' in it" + name3 := "name3" + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Name: name1, + Location: &v1.Location{File: spec1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Name: name2, + Location: &v1.Location{File: spec2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Name: name3, + Location: &v1.Location{File: spec1}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Name: name1, + Location: &v1.Location{File: spec2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Name: name2, + Location: &v1.Location{File: spec1}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Name: name3, + Location: &v1.Location{File: spec2}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.JavaScriptCypressSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["spec"] < substitutions[j]["spec"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "spec": `path/to/spec with '"'"' in it.js`, + "grep": `grep='name2 with '"'"' in it'`, + }, + { + "spec": "spec1.js", + "grep": "grep='name1; name3'", + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "npx cypress run --spec '{{ spec }}' --env {{ grep }}", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + spec1 := "spec1.js" + spec2 := "path/to/spec with ' in it.js" + + name1 := "name1" + name2 := "name2 with ' in it" + name3 := "name3" + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Name: name1, + Location: &v1.Location{File: spec1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Name: name2, + Location: &v1.Location{File: spec2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Name: name3, + Location: &v1.Location{File: spec1}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Name: name1, + Location: &v1.Location{File: spec2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Name: name2, + Location: &v1.Location{File: spec1}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Name: name3, + Location: &v1.Location{File: spec2}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.JavaScriptCypressSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["spec"] < substitutions[j]["spec"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "spec": "spec1.js", + "grep": "grep='name1'", + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/javascript_jest_substitution.go b/internal/captain/targetedretries/javascript_jest_substitution.go new file mode 100644 index 0000000..805fe74 --- /dev/null +++ b/internal/captain/targetedretries/javascript_jest_substitution.go @@ -0,0 +1,90 @@ +package targetedretries + +import ( + "fmt" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JavaScriptJestSubstitution struct{} + +func (s JavaScriptJestSubstitution) Example() string { + return "npx jest --testPathPattern '{{ testPathPattern }}' --testNamePattern '{{ testNamePattern }}'" +} + +func (s JavaScriptJestSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError( + "Retrying Jest requires a template with the 'testPathPattern' and 'testNamePattern' keywords; " + + "no keywords were found", + ) + } + + if len(keywords) != 2 { + return errors.NewInputError( + "Retrying Jest requires a template with the 'testPathPattern' and 'testNamePattern' keywords; "+ + "these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if (keywords[0] == "testPathPattern" && keywords[1] == "testNamePattern") || + (keywords[0] == "testNamePattern" && keywords[1] == "testPathPattern") { + return nil + } + + return errors.NewInputError( + "Retrying Jest requires a template with the 'testPathPattern' and 'testNamePattern' keywords; "+ + "'%v' and '%v' were found instead", + keywords[0], + keywords[1], + ) +} + +func (s JavaScriptJestSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + testsByFile := map[string][]string{} + testsSeenByFile := map[string]map[string]struct{}{} + + for _, test := range testResults.Tests { + if !filter(test) { + continue + } + + file := templating.ShellEscape(test.Location.File) + if _, ok := testsSeenByFile[file]; !ok { + testsSeenByFile[file] = map[string]struct{}{} + } + if _, ok := testsByFile[file]; !ok { + testsByFile[file] = make([]string, 0) + } + + formattedName := templating.ShellEscape(templating.RegexpEscape(strings.Join(test.Lineage, " "))) + if _, ok := testsSeenByFile[file][formattedName]; ok { + continue + } + + testsByFile[file] = append(testsByFile[file], formattedName) + testsSeenByFile[file][formattedName] = struct{}{} + } + + substitutions := make([]map[string]string, len(testsByFile)) + i := 0 + for file, tests := range testsByFile { + substitutions[i] = map[string]string{ + "testPathPattern": file, + "testNamePattern": fmt.Sprintf("^%v$", strings.Join(tests, "|")), + } + i++ + } + + return substitutions, nil +} diff --git a/internal/captain/targetedretries/javascript_jest_substitution_test.go b/internal/captain/targetedretries/javascript_jest_substitution_test.go new file mode 100644 index 0000000..f19c9b4 --- /dev/null +++ b/internal/captain/targetedretries/javascript_jest_substitution_test.go @@ -0,0 +1,261 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptJestSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.JavaScriptJestSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.JavaScriptJestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/jest.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptJestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + if substitutions[i]["testPathPattern"] != substitutions[j]["testPathPattern"] { + return substitutions[i]["testPathPattern"] < substitutions[j]["testPathPattern"] + } + + return substitutions[i]["testNamePattern"] < substitutions[j]["testNamePattern"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.JavaScriptJestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.JavaScriptJestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("npx jest") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too few placeholders", func() { + substitution := targetedretries.JavaScriptJestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx jest --testPathPattern '{{ testPathPattern }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with additional placeholders", func() { + substitution := targetedretries.JavaScriptJestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx jest --testPathPattern '{{ testPathPattern }}' --testNamePattern '{{ testNamePattern }}' {{ foo }}", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with incorrect placeholders", func() { + substitution := targetedretries.JavaScriptJestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx jest --testPathPattern '{{ wat }}' --testNamePattern '{{ foo }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with the testPathPattern and testNamePattern placeholders", func() { + substitution := targetedretries.JavaScriptJestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx jest --testPathPattern '{{ testPathPattern }}' --testNamePattern '{{ testNamePattern }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns tests grouped by file", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "npx jest --testPathPattern '{{ testPathPattern }}' --testNamePattern '{{ testNamePattern }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.js" + file2 := "path/to/file with '.js" + + lineage1 := []string{"name of describe", "test 'one' + 1"} + lineage2 := []string{"name of describe", "test 2"} + lineage3 := []string{"name of describe", "test 3"} + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Lineage: lineage1, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage1, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Lineage: lineage3, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Lineage: lineage3, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.JavaScriptJestSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["testPathPattern"] < substitutions[j]["testPathPattern"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "testPathPattern": `path/to/file with '"'"'.js`, + "testNamePattern": `^name of describe test '"'"'one'"'"' \+ 1$`, + }, + { + "testPathPattern": "path/to/file1.js", + "testNamePattern": `^name of describe test '"'"'one'"'"' \+ 1|name of describe test 2$`, + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "npx jest --testPathPattern '{{ testPathPattern }}' --testNamePattern '{{ testNamePattern }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.js" + file2 := "path/to/file with '.js" + + lineage1 := []string{"name of describe", "test 'one' + 1"} + lineage2 := []string{"name of describe", "test 2"} + lineage3 := []string{"name of describe", "test 3"} + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Lineage: lineage1, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage1, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Lineage: lineage3, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Lineage: lineage3, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.JavaScriptJestSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["testPathPattern"] < substitutions[j]["testPathPattern"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "testPathPattern": "path/to/file1.js", + "testNamePattern": `^name of describe test '"'"'one'"'"' \+ 1$`, + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/javascript_mocha_substitution.go b/internal/captain/targetedretries/javascript_mocha_substitution.go new file mode 100644 index 0000000..01b19cb --- /dev/null +++ b/internal/captain/targetedretries/javascript_mocha_substitution.go @@ -0,0 +1,87 @@ +package targetedretries + +import ( + "fmt" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JavaScriptMochaSubstitution struct{} + +func (s JavaScriptMochaSubstitution) Example() string { + return "npx mocha '{{ file }}' --grep '{{ grep }}'" +} + +func (s JavaScriptMochaSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError( + "Retrying Mocha requires a template with the 'file' and 'grep' keywords; no keywords were found", + ) + } + + if len(keywords) != 2 { + return errors.NewInputError( + "Retrying Mocha requires a template with the 'file' and 'grep' keywords; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if (keywords[0] == "file" && keywords[1] == "grep") || + (keywords[0] == "grep" && keywords[1] == "file") { + return nil + } + + return errors.NewInputError( + "Retrying Mocha requires a template with the 'file' and 'grep' keywords; '%v' and '%v' were found instead", + keywords[0], + keywords[1], + ) +} + +func (s JavaScriptMochaSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + testsByFile := map[string][]string{} + testsSeenByFile := map[string]map[string]struct{}{} + + for _, test := range testResults.Tests { + if !filter(test) { + continue + } + + file := templating.ShellEscape(test.Location.File) + if _, ok := testsSeenByFile[file]; !ok { + testsSeenByFile[file] = map[string]struct{}{} + } + if _, ok := testsByFile[file]; !ok { + testsByFile[file] = make([]string, 0) + } + + formattedName := templating.ShellEscape(templating.RegexpEscape(test.Name)) + if _, ok := testsSeenByFile[file][formattedName]; ok { + continue + } + + testsByFile[file] = append(testsByFile[file], formattedName) + testsSeenByFile[file][formattedName] = struct{}{} + } + + substitutions := make([]map[string]string, len(testsByFile)) + i := 0 + for file, tests := range testsByFile { + substitutions[i] = map[string]string{ + "file": file, + "grep": fmt.Sprintf("^%v$", strings.Join(tests, "|")), + } + i++ + } + + return substitutions, nil +} diff --git a/internal/captain/targetedretries/javascript_mocha_substitution_test.go b/internal/captain/targetedretries/javascript_mocha_substitution_test.go new file mode 100644 index 0000000..37936ee --- /dev/null +++ b/internal/captain/targetedretries/javascript_mocha_substitution_test.go @@ -0,0 +1,261 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptMochaSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.JavaScriptMochaSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.JavaScriptMochaSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/mocha.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptMochaParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + if substitutions[i]["file"] != substitutions[j]["file"] { + return substitutions[i]["file"] < substitutions[j]["file"] + } + + return substitutions[i]["grep"] < substitutions[j]["grep"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.JavaScriptMochaSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.JavaScriptMochaSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("npx mocha") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too few placeholders", func() { + substitution := targetedretries.JavaScriptMochaSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx mocha '{{ file }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with additional placeholders", func() { + substitution := targetedretries.JavaScriptMochaSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx mocha '{{ file }}' --grep '{{ grep }}' {{ foo }}", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with incorrect placeholders", func() { + substitution := targetedretries.JavaScriptMochaSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx mocha '{{ wat }}' --grep '{{ foo }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with the file and grep placeholders", func() { + substitution := targetedretries.JavaScriptMochaSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx mocha '{{ file }}' --grep '{{ grep }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns tests grouped by file", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "npx mocha '{{ file }}' --grep '{{ grep }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.js" + file2 := "path/to/file with '.js" + + name1 := "name of describe test 'one' + 1" + name2 := "name of describe test 2" + name3 := "name of describe test 3" + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Name: name1, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Name: name1, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Name: name2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Name: name2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Name: name3, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Name: name3, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.JavaScriptMochaSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["file"] < substitutions[j]["file"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "file": `path/to/file with '"'"'.js`, + "grep": `^name of describe test '"'"'one'"'"' \+ 1$`, + }, + { + "file": "path/to/file1.js", + "grep": `^name of describe test '"'"'one'"'"' \+ 1|name of describe test 2$`, + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "npx mocha '{{ file }}' --grep '{{ grep }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.js" + file2 := "path/to/file with '.js" + + name1 := "name of describe test 'one' + 1" + name2 := "name of describe test 2" + name3 := "name of describe test 3" + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Name: name1, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Name: name1, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Name: name2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Name: name2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Name: name3, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Name: name3, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.JavaScriptMochaSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["file"] < substitutions[j]["file"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "file": "path/to/file1.js", + "grep": `^name of describe test '"'"'one'"'"' \+ 1$`, + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/javascript_playwright_substitution.go b/internal/captain/targetedretries/javascript_playwright_substitution.go new file mode 100644 index 0000000..168d0ca --- /dev/null +++ b/internal/captain/targetedretries/javascript_playwright_substitution.go @@ -0,0 +1,168 @@ +package targetedretries + +import ( + "fmt" + "sort" + "strconv" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JavaScriptPlaywrightSubstitution struct{} + +func (s JavaScriptPlaywrightSubstitution) Example() string { + return "npx playwright test {{ tests }} --project '{{ project }}'" +} + +func (s JavaScriptPlaywrightSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError( + "Retrying Playwright requires a template with the 'tests' and 'project' keywords " + + "or a template with the 'file', 'project', and 'grep' keywords; no keywords were found", + ) + } + + if len(keywords) != 2 && len(keywords) != 3 { + return errors.NewInputError( + "Retrying Playwright requires a template with the 'tests' and 'project' keywords "+ + "or a template with the 'file', 'project', and 'grep' keywords; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + sort.SliceStable(keywords, func(i, j int) bool { + return keywords[i] < keywords[j] + }) + + if len(keywords) == 2 { + if keywords[0] == "project" && keywords[1] == "tests" { + return nil + } + + return errors.NewInputError( + "Retrying Playwright requires a template with the 'tests' and 'project' keywords "+ + "or a template with the 'file', 'project', and 'grep' keywords; "+ + "'%v' and '%v' were found instead", + keywords[0], + keywords[1], + ) + } + + // else, len(keywords) == 3 + + if keywords[0] == "file" && keywords[1] == "grep" && keywords[2] == "project" { + return nil + } + + return errors.NewInputError( + "Retrying Playwright requires a template with the 'tests' and 'project' keywords "+ + "or a template with the 'file', 'project', and 'grep' keywords; "+ + "'%v', '%v', and '%v' were found instead", + keywords[0], + keywords[1], + keywords[2], + ) +} + +func (s JavaScriptPlaywrightSubstitution) SubstitutionsFor( + compiledTemplate templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 2 { + // tests and project + + testsByProject := map[string][]string{} + testsSeenByProject := map[string]map[string]struct{}{} + + for _, test := range testResults.Tests { + if !filter(test) { + continue + } + + project := templating.ShellEscape(test.Attempt.Meta["project"].(string)) + file := templating.ShellEscape(test.Location.File) + line := strconv.Itoa(*test.Location.Line) + test := fmt.Sprintf("%v:%v", file, line) + + if _, ok := testsSeenByProject[project]; !ok { + testsSeenByProject[project] = map[string]struct{}{} + } + if _, ok := testsByProject[project]; !ok { + testsByProject[project] = make([]string, 0) + } + + if _, ok := testsSeenByProject[project][test]; ok { + continue + } + + testsByProject[project] = append(testsByProject[project], test) + testsSeenByProject[project][test] = struct{}{} + } + + substitutions := make([]map[string]string, 0) + for project, tests := range testsByProject { + substitutions = append(substitutions, map[string]string{ + "project": project, + "tests": strings.Join(tests, " "), + }) + } + + return substitutions, nil + } + + // else, len(keywords) == 3 + // file, project, and grep + + testsByFileByProject := map[string]map[string][]string{} + testsSeenByFileByProject := map[string]map[string]map[string]struct{}{} + + for _, test := range testResults.Tests { + if !filter(test) { + continue + } + + project := templating.ShellEscape(test.Attempt.Meta["project"].(string)) + file := templating.ShellEscape(test.Location.File) + if _, ok := testsSeenByFileByProject[project]; !ok { + testsSeenByFileByProject[project] = map[string]map[string]struct{}{} + } + if _, ok := testsSeenByFileByProject[project][file]; !ok { + testsSeenByFileByProject[project][file] = map[string]struct{}{} + } + if _, ok := testsByFileByProject[project]; !ok { + testsByFileByProject[project] = map[string][]string{} + } + if _, ok := testsByFileByProject[project][file]; !ok { + testsByFileByProject[project][file] = make([]string, 0) + } + + formattedName := templating.ShellEscape(templating.RegexpEscape(test.Name)) + if _, ok := testsSeenByFileByProject[project][file][formattedName]; ok { + continue + } + + testsByFileByProject[project][file] = append(testsByFileByProject[project][file], formattedName) + testsSeenByFileByProject[project][file][formattedName] = struct{}{} + } + + substitutions := make([]map[string]string, 0) + for project, testsByFile := range testsByFileByProject { + for file, tests := range testsByFile { + substitutions = append(substitutions, map[string]string{ + "project": project, + "file": file, + "grep": strings.Join(tests, "|"), + }) + } + } + + return substitutions, nil +} diff --git a/internal/captain/targetedretries/javascript_playwright_substitution_test.go b/internal/captain/targetedretries/javascript_playwright_substitution_test.go new file mode 100644 index 0000000..a9faee0 --- /dev/null +++ b/internal/captain/targetedretries/javascript_playwright_substitution_test.go @@ -0,0 +1,584 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptPlaywrightSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.JavaScriptPlaywrightSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with the example template", func() { + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/playwright.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptPlaywrightParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + if substitutions[i]["tests"] != substitutions[j]["tests"] { + return substitutions[i]["tests"] < substitutions[j]["tests"] + } + + return substitutions[i]["project"] < substitutions[j]["project"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + It("works with the old template format", func() { + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx playwright test '{{ file }}' --project '{{ project }}' --grep '{{ grep }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/playwright.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptPlaywrightParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + if substitutions[i]["file"] != substitutions[j]["file"] { + return substitutions[i]["file"] < substitutions[j]["file"] + } + + if substitutions[i]["project"] != substitutions[j]["project"] { + return substitutions[i]["project"] < substitutions[j]["project"] + } + + return substitutions[i]["grep"] < substitutions[j]["grep"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + It("works with the new template format", func() { + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx playwright test {{ tests }} --project '{{ project }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/playwright.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptPlaywrightParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + if substitutions[i]["tests"] != substitutions[j]["tests"] { + return substitutions[i]["tests"] < substitutions[j]["tests"] + } + + return substitutions[i]["project"] < substitutions[j]["project"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("npx playwright test") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too few placeholders", func() { + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx playwright test '{{ file }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with additional placeholders", func() { + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx playwright test '{{ file }}' --project '{{ project }}' --grep '{{ grep }}' {{ foo }}", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with incorrect placeholders for the old format", func() { + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx playwright test '{{ wat }}' --project '{{ who }}' --grep '{{ foo }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with incorrect placeholders for the new format", func() { + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx playwright test {{ wat }} --project '{{ who }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with the file, project, and grep placeholders", func() { + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx playwright test '{{ file }}' --project '{{ project }}' --grep '{{ grep }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is valid for a template with the tests and project placeholders", func() { + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx playwright test {{ tests }} --project '{{ project }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + Describe("the old template format", func() { + It("returns tests grouped by file and project", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "npx playwright test '{{ file }}' --project '{{ project }}' --grep '{{ grep }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.js" + file2 := "path/to/file with '.js" + + project1 := "project1" + project2 := "project with ' 2" + + name1 := "name of describe test 'one' + 1" + name2 := "name of describe test 2" + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Name: name1, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project1}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project1}, + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Name: name1, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project2}, + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project2}, + Status: v1.NewPendedTestStatus(nil), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project2}, + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project1}, + Status: v1.NewSkippedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["file"] < substitutions[j]["file"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "file": `path/to/file with '"'"'.js`, + "project": `project with '"'"' 2`, + "grep": `name of describe test '"'"'one'"'"' \+ 1`, + }, + { + "file": "path/to/file1.js", + "project": "project1", + "grep": `name of describe test '"'"'one'"'"' \+ 1|name of describe test 2`, + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "npx playwright test '{{ file }}' --project '{{ project }}' --grep '{{ grep }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.js" + file2 := "path/to/file with '.js" + + project1 := "project1" + project2 := "project with ' 2" + + name1 := "name of describe test 'one' + 1" + name2 := "name of describe test 2" + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Name: name1, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project1}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project1}, + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Name: name1, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project2}, + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project2}, + Status: v1.NewPendedTestStatus(nil), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project2}, + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project1}, + Status: v1.NewSkippedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["file"] < substitutions[j]["file"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "file": "path/to/file1.js", + "project": "project1", + "grep": `name of describe test '"'"'one'"'"' \+ 1`, + }, + }, + )) + }) + }) + + Describe("the new template format", func() { + It("returns tests grouped by file and project", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "npx playwright test {{ tests }} --project '{{ project }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.js" + file2 := "path/to/file with '.js" + + project1 := "project1" + project2 := "project with ' 2" + + name1 := "name of describe test 'one' + 1" + name2 := "name of describe test 2" + + lineOne := 1 + lineTen := 10 + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Name: name1, + Location: &v1.Location{File: file1, Line: &lineOne}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project1}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file1, Line: &lineTen}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project1}, + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Name: name1, + Location: &v1.Location{File: file2, Line: &lineTen}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project2}, + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project2}, + Status: v1.NewPendedTestStatus(nil), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project2}, + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project1}, + Status: v1.NewSkippedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["tests"] < substitutions[j]["tests"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "project": "project with '\"'\"' 2", + "tests": "path/to/file with '\"'\"'.js:10", + }, + { + "project": "project1", + "tests": "path/to/file1.js:1 path/to/file1.js:10", + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "npx playwright test {{ tests }} --project '{{ project }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.js" + file2 := "path/to/file with '.js" + + project1 := "project1" + project2 := "project with ' 2" + + name1 := "name of describe test 'one' + 1" + name2 := "name of describe test 2" + + lineOne := 1 + lineTen := 10 + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Name: name1, + Location: &v1.Location{File: file1, Line: &lineOne}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project1}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file1, Line: &lineTen}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project1}, + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Name: name1, + Location: &v1.Location{File: file2, Line: &lineTen}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project2}, + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project2}, + Status: v1.NewPendedTestStatus(nil), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project2}, + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Name: name2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"project": project1}, + Status: v1.NewSkippedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.JavaScriptPlaywrightSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["tests"] < substitutions[j]["tests"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "project": "project1", + "tests": "path/to/file1.js:1", + }, + }, + )) + }) + }) + }) +}) diff --git a/internal/captain/targetedretries/javascript_vitest_substitution.go b/internal/captain/targetedretries/javascript_vitest_substitution.go new file mode 100644 index 0000000..a377187 --- /dev/null +++ b/internal/captain/targetedretries/javascript_vitest_substitution.go @@ -0,0 +1,90 @@ +package targetedretries + +import ( + "fmt" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JavaScriptVitestSubstitution struct{} + +func (s JavaScriptVitestSubstitution) Example() string { + return "npx vitest run '{{ file }}' --testNamePattern '{{ testNamePattern }}'" +} + +func (s JavaScriptVitestSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError( + "Retrying Vitest requires a template with the 'file' and 'testNamePattern' keywords; " + + "no keywords were found", + ) + } + + if len(keywords) != 2 { + return errors.NewInputError( + "Retrying Vitest requires a template with the 'file' and 'testNamePattern' keywords; "+ + "these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if (keywords[0] == "file" && keywords[1] == "testNamePattern") || + (keywords[0] == "testNamePattern" && keywords[1] == "file") { + return nil + } + + return errors.NewInputError( + "Retrying Vitest requires a template with the 'file' and 'testNamePattern' keywords; "+ + "'%v' and '%v' were found instead", + keywords[0], + keywords[1], + ) +} + +func (s JavaScriptVitestSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + testsByFile := map[string][]string{} + testsSeenByFile := map[string]map[string]struct{}{} + + for _, test := range testResults.Tests { + if !filter(test) { + continue + } + + file := templating.ShellEscape(test.Location.File) + if _, ok := testsSeenByFile[file]; !ok { + testsSeenByFile[file] = map[string]struct{}{} + } + if _, ok := testsByFile[file]; !ok { + testsByFile[file] = make([]string, 0) + } + + formattedName := templating.ShellEscape(templating.RegexpEscape(strings.Join(test.Lineage, " "))) + if _, ok := testsSeenByFile[file][formattedName]; ok { + continue + } + + testsByFile[file] = append(testsByFile[file], formattedName) + testsSeenByFile[file][formattedName] = struct{}{} + } + + substitutions := make([]map[string]string, len(testsByFile)) + i := 0 + for file, tests := range testsByFile { + substitutions[i] = map[string]string{ + "file": file, + "testNamePattern": fmt.Sprintf("^%v$", strings.Join(tests, "|")), + } + i++ + } + + return substitutions, nil +} diff --git a/internal/captain/targetedretries/javascript_vitest_substitution_test.go b/internal/captain/targetedretries/javascript_vitest_substitution_test.go new file mode 100644 index 0000000..7a0b695 --- /dev/null +++ b/internal/captain/targetedretries/javascript_vitest_substitution_test.go @@ -0,0 +1,261 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JavaScriptVitestSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.JavaScriptVitestSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.JavaScriptVitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/vitest.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.JavaScriptVitestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + if substitutions[i]["file"] != substitutions[j]["file"] { + return substitutions[i]["file"] < substitutions[j]["file"] + } + + return substitutions[i]["testNamePattern"] < substitutions[j]["testNamePattern"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.JavaScriptVitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.JavaScriptVitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("npx jest") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too few placeholders", func() { + substitution := targetedretries.JavaScriptVitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx jest --file '{{ file }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with additional placeholders", func() { + substitution := targetedretries.JavaScriptVitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx jest --file '{{ file }}' --testNamePattern '{{ testNamePattern }}' {{ foo }}", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with incorrect placeholders", func() { + substitution := targetedretries.JavaScriptVitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx jest --file '{{ wat }}' --testNamePattern '{{ foo }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with the file and testNamePattern placeholders", func() { + substitution := targetedretries.JavaScriptVitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "npx jest --file '{{ file }}' --testNamePattern '{{ testNamePattern }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns tests grouped by file", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "npx jest --file '{{ file }}' --testNamePattern '{{ testNamePattern }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.js" + file2 := "path/to/file with '.js" + + lineage1 := []string{"name of describe", "test 'one' + 1"} + lineage2 := []string{"name of describe", "test 2"} + lineage3 := []string{"name of describe", "test 3"} + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Lineage: lineage1, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage1, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Lineage: lineage3, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Lineage: lineage3, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.JavaScriptVitestSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["file"] < substitutions[j]["file"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "file": `path/to/file with '"'"'.js`, + "testNamePattern": `^name of describe test '"'"'one'"'"' \+ 1$`, + }, + { + "file": "path/to/file1.js", + "testNamePattern": `^name of describe test '"'"'one'"'"' \+ 1|name of describe test 2$`, + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "npx jest --file '{{ file }}' --testNamePattern '{{ testNamePattern }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.js" + file2 := "path/to/file with '.js" + + lineage1 := []string{"name of describe", "test 'one' + 1"} + lineage2 := []string{"name of describe", "test 2"} + lineage3 := []string{"name of describe", "test 3"} + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Lineage: lineage1, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage1, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Lineage: lineage3, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Lineage: lineage3, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.JavaScriptVitestSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["file"] < substitutions[j]["file"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "file": "path/to/file1.js", + "testNamePattern": `^name of describe test '"'"'one'"'"' \+ 1$`, + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/json_substitution.go b/internal/captain/targetedretries/json_substitution.go new file mode 100644 index 0000000..425cc50 --- /dev/null +++ b/internal/captain/targetedretries/json_substitution.go @@ -0,0 +1,98 @@ +package targetedretries + +import ( + "encoding/json" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/fs" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type JSONSubstitution struct { + FileSystem fs.FileSystem +} + +func (s JSONSubstitution) Example() string { + return "bin/your-script {{ jsonFilePath }}" +} + +func (s JSONSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError( + "Retrying with JSON requires a template with the 'jsonFilePath' keyword; no keywords were found", + ) + } + + if len(keywords) > 1 { + return errors.NewInputError( + "Retrying with JSON requires a template with only the 'jsonFilePath' keyword; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if keywords[0] != "jsonFilePath" { + return errors.NewInputError( + "Retrying with JSON requires a template with only the 'jsonFilePath' keyword; '%v' was found instead", + keywords[0], + ) + } + + return nil +} + +func (s JSONSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(test v1.Test) bool, +) ([]map[string]string, error) { + testsToRetry := make([]v1.Test, 0) + + for _, test := range testResults.Tests { + if filter(test) { + testsToRetry = append(testsToRetry, test) + } + } + + file, err := s.FileSystem.CreateTemp("", "test-results-to-retry") + if err != nil { + return nil, errors.WithStack(err) + } + defer file.Close() + + testResultsToRetry := v1.NewTestResults(testResults.Framework, testsToRetry, nil) + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + if err := encoder.Encode(testResultsToRetry); err != nil { + return nil, errors.WithStack(err) + } + + return []map[string]string{{"jsonFilePath": file.Name()}}, nil +} + +func (s JSONSubstitution) CleanUp(allSubstitutions []map[string]string) error { + if len(allSubstitutions) == 0 { + return nil + } + + if len(allSubstitutions) > 1 { + return errors.NewInternalError( + "Expected JSONSubstitution to create only one substitution, but got %v", + len(allSubstitutions), + ) + } + + substitutions := allSubstitutions[0] + filePath, ok := substitutions["jsonFilePath"] + if !ok { + return errors.NewInternalError( + "Expected JSONSubstitution to expose the file path in the substitution, but it did not", + ) + } + + err := s.FileSystem.Remove(filePath) + return errors.Wrapf(err, "Unabled to clean up %q", filePath) +} diff --git a/internal/captain/targetedretries/json_substitution_test.go b/internal/captain/targetedretries/json_substitution_test.go new file mode 100644 index 0000000..fbb11a4 --- /dev/null +++ b/internal/captain/targetedretries/json_substitution_test.go @@ -0,0 +1,252 @@ +package targetedretries_test + +import ( + "encoding/json" + "os" + "strings" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/fs" + "github.com/rwx-cloud/cli/internal/captain/mocks" + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("JSONSubstitution", func() { + var ( + mockFileSystem *mocks.FileSystem + mockFile *mocks.File + ) + + BeforeEach(func() { + mockFileSystem = new(mocks.FileSystem) + mockFile = new(mocks.File) + mockFile.Builder = new(strings.Builder) + mockFile.MockName = func() string { + return "json-temp-file" + } + mockFileSystem.MockCreateTemp = func(_, _ string) (fs.File, error) { + return mockFile, nil + } + }) + + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/rspec.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyRSpecParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(substitutions).To(HaveLen(1)) + cupaloy.SnapshotT(GinkgoT(), mockFile.String()) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + compiledTemplate, compileErr := templating.CompileTemplate("bin/your-script") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too many placeholders", func() { + substitution := targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + compiledTemplate, compileErr := templating.CompileTemplate("bin/your-script {{ file }} {{ jsonFilePath }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template without a jsonFilePath placeholder", func() { + substitution := targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + compiledTemplate, compileErr := templating.CompileTemplate("bin/your-script {{ file }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with only a jsonFilePath placeholder", func() { + substitution := targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + compiledTemplate, compileErr := templating.CompileTemplate("bin/your-script {{ jsonFilePath }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns the path of a temporary file", func() { + compiledTemplate, compileErr := templating.CompileTemplate("bin/your-script {{ jsonFilePath }}") + Expect(compileErr).NotTo(HaveOccurred()) + + id1 := "/path/to/file with spaces.rb:10" + id2 := "/path/to/filewith'.rb:15" + id3 := "/path/to/otherfile.rb:[1:2:2:5]" + id4 := "/path/to/otherfile.rb:[1:3:2:5]" + id5 := "/path/to/otherfile.rb:[1:4:2:5]" + id6 := "/path/to/otherfile.rb:[1:5:2:5]" + testResults := v1.TestResults{ + Tests: []v1.Test{ + {ID: &id1, Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + {ID: &id2, Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}}, + {ID: &id3, Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}}, + {ID: &id4, Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}}, + {ID: &id5, Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}}, + {ID: &id6, Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}}, + }, + } + + substitution := targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + Expect(substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + )).To(Equal( + []map[string]string{ + { + "jsonFilePath": "json-temp-file", + }, + }, + )) + + parsedTestResults := v1.TestResults{} + decoder := json.NewDecoder(strings.NewReader(mockFile.String())) + err := decoder.Decode(&parsedTestResults) + Expect(err).NotTo(HaveOccurred()) + Expect(parsedTestResults.Summary.Tests).To(Equal(3)) + Expect(parsedTestResults.Summary.Failed).To(Equal(1)) + Expect(parsedTestResults.Summary.Canceled).To(Equal(1)) + Expect(parsedTestResults.Summary.TimedOut).To(Equal(1)) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate("bin/your-script {{ jsonFilePath }}") + Expect(compileErr).NotTo(HaveOccurred()) + + id1 := "/path/to/file with spaces.rb:10" + id2 := "/path/to/filewith'.rb:15" + id3 := "/path/to/otherfile.rb:[1:2:2:5]" + id4 := "/path/to/otherfile.rb:[1:3:2:5]" + id5 := "/path/to/otherfile.rb:[1:4:2:5]" + id6 := "/path/to/otherfile.rb:[1:5:2:5]" + testResults := v1.TestResults{ + Tests: []v1.Test{ + {ID: &id1, Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + {ID: &id2, Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}}, + {ID: &id3, Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}}, + {ID: &id4, Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}}, + {ID: &id5, Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}}, + {ID: &id6, Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}}, + }, + } + + substitution := targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "jsonFilePath": "json-temp-file", + }, + }, + )) + + parsedTestResults := v1.TestResults{} + decoder := json.NewDecoder(strings.NewReader(mockFile.String())) + err = decoder.Decode(&parsedTestResults) + Expect(err).NotTo(HaveOccurred()) + Expect(parsedTestResults.Summary.Tests).To(Equal(1)) + Expect(parsedTestResults.Summary.Failed).To(Equal(1)) + Expect(parsedTestResults.Summary.Canceled).To(Equal(0)) + Expect(parsedTestResults.Summary.TimedOut).To(Equal(0)) + }) + }) + + Describe("CleanUp", func() { + It("errs with more than one set of substitutions", func() { + substitution := targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + err := substitution.CleanUp([]map[string]string{{}, {}}) + Expect(err).To(HaveOccurred()) + }) + + It("returns nil with no sets of substitutions", func() { + substitution := targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + err := substitution.CleanUp([]map[string]string{}) + Expect(err).NotTo(HaveOccurred()) + }) + + It("errs when the substitution doesn't have the file path", func() { + substitution := targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + err := substitution.CleanUp([]map[string]string{{"not-the-path": ""}}) + Expect(err).To(HaveOccurred()) + }) + + It("errs when the file at the path in the substitution can't be removed", func() { + mockFileSystem.MockRemove = func(_ string) error { + return errors.NewInternalError("uh oh") + } + + substitution := targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + err := substitution.CleanUp([]map[string]string{{"jsonFilePath": "the-path"}}) + Expect(err).To(HaveOccurred()) + }) + + It("removes the file at the path in the substitution", func() { + calledWith := "" + mockFileSystem.MockRemove = func(name string) error { + calledWith = name + return nil + } + + substitution := targetedretries.JSONSubstitution{FileSystem: mockFileSystem} + err := substitution.CleanUp([]map[string]string{{"jsonFilePath": "the-path"}}) + Expect(err).NotTo(HaveOccurred()) + Expect(calledWith).To(Equal("the-path")) + }) + }) +}) diff --git a/internal/captain/targetedretries/phpunit_substitution.go b/internal/captain/targetedretries/phpunit_substitution.go new file mode 100644 index 0000000..5d5c0de --- /dev/null +++ b/internal/captain/targetedretries/phpunit_substitution.go @@ -0,0 +1,81 @@ +package targetedretries + +import ( + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type PHPUnitSubstitution struct{} + +func (s PHPUnitSubstitution) Example() string { + return "vendor/bin/phpunit --filter '{{ filter }}' '{{ file }}'" +} + +func (s PHPUnitSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError( + "Retrying Mocha requires a template with the 'filter' and 'file' keywords; no keywords were found", + ) + } + + if len(keywords) != 2 { + return errors.NewInputError( + "Retrying Mocha requires a template with the 'filter' and 'file' keywords; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if (keywords[0] == "filter" && keywords[1] == "file") || + (keywords[0] == "file" && keywords[1] == "filter") { + return nil + } + + return errors.NewInputError( + "Retrying Mocha requires a template with the 'filter' and 'file' keywords; '%v' and '%v' were found instead", + keywords[0], + keywords[1], + ) +} + +func (s PHPUnitSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + testsSeenByFile := map[string]map[string]struct{}{} + + for _, test := range testResults.Tests { + if !filter(test) { + continue + } + + file := templating.ShellEscape(test.Location.File) + if _, ok := testsSeenByFile[file]; !ok { + testsSeenByFile[file] = map[string]struct{}{} + } + + methodName := test.Attempt.Meta["name"].(string) + if _, ok := testsSeenByFile[file][methodName]; ok { + continue + } + + testsSeenByFile[file][methodName] = struct{}{} + } + + substitutions := make([]map[string]string, 0) + for file, testsSeen := range testsSeenByFile { + for methodName := range testsSeen { + substitutions = append(substitutions, map[string]string{ + "file": file, + "filter": methodName, + }) + } + } + + return substitutions, nil +} diff --git a/internal/captain/targetedretries/phpunit_substitution_test.go b/internal/captain/targetedretries/phpunit_substitution_test.go new file mode 100644 index 0000000..f7c4e00 --- /dev/null +++ b/internal/captain/targetedretries/phpunit_substitution_test.go @@ -0,0 +1,267 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("PHPUnitSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.PHPUnitSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.PHPUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/phpunit.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.PHPUnitParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + if substitutions[i]["file"] != substitutions[j]["file"] { + return substitutions[i]["file"] < substitutions[j]["file"] + } + + return substitutions[i]["filter"] < substitutions[j]["filter"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.PHPUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.PHPUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("vendor/bin/phpunit") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too few placeholders", func() { + substitution := targetedretries.PHPUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "vendor/bin/phpunit '{{ file }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with additional placeholders", func() { + substitution := targetedretries.PHPUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "vendor/bin/phpunit --filter '{{ filter }}' '{{ file }}' {{ foo }}", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with incorrect placeholders", func() { + substitution := targetedretries.PHPUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "vendor/bin/phpunit --filter '{{ foo }}' '{{ bar }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with the file and grep placeholders", func() { + substitution := targetedretries.PHPUnitSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate( + "vendor/bin/phpunit --filter '{{ filter }}' '{{ file }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns a substitution per file/test", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "vendor/bin/phpunit '{{ file }}' --grep '{{ grep }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.php" + file2 := "path/to/file with '.php" + + name1 := "some_test_name" + name2 := "other_test_name" + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"name": name1}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"name": name1}, + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"name": name2}, + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"name": name2}, + Status: v1.NewPendedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.PHPUnitSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + if substitutions[i]["file"] != substitutions[j]["file"] { + return substitutions[i]["file"] < substitutions[j]["file"] + } + + return substitutions[i]["filter"] < substitutions[j]["filter"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "file": `path/to/file with '"'"'.php`, + "filter": "some_test_name", + }, + { + "file": "path/to/file1.php", + "filter": "other_test_name", + }, + { + "file": "path/to/file1.php", + "filter": "some_test_name", + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate( + "vendor/bin/phpunit '{{ file }}' --grep '{{ grep }}'", + ) + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "path/to/file1.php" + file2 := "path/to/file with '.php" + + name1 := "some_test_name" + name2 := "other_test_name" + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"name": name1}, + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"name": name1}, + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"name": name2}, + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{"name": name2}, + Status: v1.NewPendedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.PHPUnitSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + if substitutions[i]["file"] != substitutions[j]["file"] { + return substitutions[i]["file"] < substitutions[j]["file"] + } + + return substitutions[i]["filter"] < substitutions[j]["filter"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "file": "path/to/file1.php", + "filter": "some_test_name", + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/python_pytest_substitution.go b/internal/captain/targetedretries/python_pytest_substitution.go new file mode 100644 index 0000000..e4a924a --- /dev/null +++ b/internal/captain/targetedretries/python_pytest_substitution.go @@ -0,0 +1,63 @@ +package targetedretries + +import ( + "fmt" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type PythonPytestSubstitution struct{} + +func (s PythonPytestSubstitution) Example() string { + return "pytest {{ tests }}" +} + +func (s PythonPytestSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError("Retrying pytest requires a template with the 'tests' keyword; no keywords were found") + } + + if len(keywords) > 1 { + return errors.NewInputError( + "Retrying pytest requires a template with only the 'tests' keyword; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if keywords[0] != "tests" { + return errors.NewInputError( + "Retrying pytest requires a template with only the 'tests' keyword; '%v' was found instead", + keywords[0], + ) + } + + return nil +} + +func (s PythonPytestSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + testIdentifiers := make([]string, 0) + + for _, test := range testResults.Tests { + if filter(test) { + testIdentifiers = append( + testIdentifiers, + fmt.Sprintf("'%v'", templating.ShellEscape(*test.ID)), + ) + } + } + + if len(testIdentifiers) > 0 { + return []map[string]string{{"tests": strings.Join(testIdentifiers, " ")}}, nil + } + + return []map[string]string{}, nil +} diff --git a/internal/captain/targetedretries/python_pytest_substitution_test.go b/internal/captain/targetedretries/python_pytest_substitution_test.go new file mode 100644 index 0000000..8d1bb2b --- /dev/null +++ b/internal/captain/targetedretries/python_pytest_substitution_test.go @@ -0,0 +1,174 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("PythonPytestSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.PythonPytestSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.PythonPytestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/pytest_reportlog.jsonl") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.PythonPytestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["tests"] < substitutions[j]["tests"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.PythonPytestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.PythonPytestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("pytest") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too many placeholders", func() { + substitution := targetedretries.PythonPytestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("pytest {{ file }} {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template without a tests placeholder", func() { + substitution := targetedretries.PythonPytestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("pytest {{ file }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with only a tests placeholder", func() { + substitution := targetedretries.PythonPytestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("pytest {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns the shell escaped test identifiers", func() { + compiledTemplate, compileErr := templating.CompileTemplate("pytest {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + id1 := "SomeClass.some_method" + id2 := "Some ' Class.with_single_quotes" + id3 := "some_method" + id4 := "id4" + id5 := "id5" + id6 := "id6" + testResults := v1.TestResults{ + Tests: []v1.Test{ + {ID: &id1, Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + {ID: &id2, Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}}, + {ID: &id3, Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}}, + {ID: &id4, Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}}, + {ID: &id5, Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}}, + {ID: &id6, Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}}, + }, + } + + substitution := targetedretries.PythonPytestSubstitution{} + Expect(substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + )).To(Equal( + []map[string]string{ + { + "tests": `'SomeClass.some_method' ` + + `'Some '"'"' Class.with_single_quotes' ` + + `'some_method'`, + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate("pytest {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + id1 := "SomeClass.some_method" + id2 := "Some ' Class.with_single_quotes" + id3 := "some_method" + id4 := "id4" + id5 := "id5" + id6 := "id6" + testResults := v1.TestResults{ + Tests: []v1.Test{ + {ID: &id1, Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + {ID: &id2, Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}}, + {ID: &id3, Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}}, + {ID: &id4, Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}}, + {ID: &id5, Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}}, + {ID: &id6, Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}}, + }, + } + + substitution := targetedretries.PythonPytestSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "tests": "'SomeClass.some_method'", + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/python_unittest_substitution.go b/internal/captain/targetedretries/python_unittest_substitution.go new file mode 100644 index 0000000..9109ebd --- /dev/null +++ b/internal/captain/targetedretries/python_unittest_substitution.go @@ -0,0 +1,63 @@ +package targetedretries + +import ( + "fmt" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type PythonUnitTestSubstitution struct{} + +func (s PythonUnitTestSubstitution) Example() string { + return "python -m xmlrunner {{ tests }}" +} + +func (s PythonUnitTestSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError("Retrying unittest requires a template with the 'tests' keyword; no keywords were found") + } + + if len(keywords) > 1 { + return errors.NewInputError( + "Retrying unittest requires a template with only the 'tests' keyword; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if keywords[0] != "tests" { + return errors.NewInputError( + "Retrying unittest requires a template with only the 'tests' keyword; '%v' was found instead", + keywords[0], + ) + } + + return nil +} + +func (s PythonUnitTestSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + testIdentifiers := make([]string, 0) + + for _, test := range testResults.Tests { + if filter(test) { + testIdentifiers = append( + testIdentifiers, + fmt.Sprintf("'%v'", templating.ShellEscape(test.Name)), + ) + } + } + + if len(testIdentifiers) > 0 { + return []map[string]string{{"tests": strings.Join(testIdentifiers, " ")}}, nil + } + + return []map[string]string{}, nil +} diff --git a/internal/captain/targetedretries/python_unittest_substitution_test.go b/internal/captain/targetedretries/python_unittest_substitution_test.go new file mode 100644 index 0000000..b0a506d --- /dev/null +++ b/internal/captain/targetedretries/python_unittest_substitution_test.go @@ -0,0 +1,174 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("PythonUnitTestSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.PythonUnitTestSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.PythonUnitTestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/unittest.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.PythonUnitTestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["tests"] < substitutions[j]["tests"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.PythonUnitTestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.PythonUnitTestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("unittest") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too many placeholders", func() { + substitution := targetedretries.PythonUnitTestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("unittest {{ file }} {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template without a tests placeholder", func() { + substitution := targetedretries.PythonUnitTestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("unittest {{ file }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with only a tests placeholder", func() { + substitution := targetedretries.PythonUnitTestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("unittest {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns the shell escaped test identifiers", func() { + compiledTemplate, compileErr := templating.CompileTemplate("unittest {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + name1 := "SomeClass.some_method" + name2 := "Some ' Class.with_single_quotes" + name3 := "some_method" + name4 := "name4" + name5 := "name5" + name6 := "name6" + testResults := v1.TestResults{ + Tests: []v1.Test{ + {Name: name1, Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + {Name: name2, Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}}, + {Name: name3, Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}}, + {Name: name4, Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}}, + {Name: name5, Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}}, + {Name: name6, Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}}, + }, + } + + substitution := targetedretries.PythonUnitTestSubstitution{} + Expect(substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + )).To(Equal( + []map[string]string{ + { + "tests": `'SomeClass.some_method' ` + + `'Some '"'"' Class.with_single_quotes' ` + + `'some_method'`, + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate("unittest {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + name1 := "SomeClass.some_method" + name2 := "Some ' Class.with_single_quotes" + name3 := "some_method" + name4 := "name4" + name5 := "name5" + name6 := "name6" + testResults := v1.TestResults{ + Tests: []v1.Test{ + {Name: name1, Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + {Name: name2, Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}}, + {Name: name3, Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}}, + {Name: name4, Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}}, + {Name: name5, Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}}, + {Name: name6, Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}}, + }, + } + + substitution := targetedretries.PythonUnitTestSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "tests": "'SomeClass.some_method'", + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/ruby_cucumber_substitution.go b/internal/captain/targetedretries/ruby_cucumber_substitution.go new file mode 100644 index 0000000..09fdd0b --- /dev/null +++ b/internal/captain/targetedretries/ruby_cucumber_substitution.go @@ -0,0 +1,73 @@ +package targetedretries + +import ( + "fmt" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type RubyCucumberSubstitution struct{} + +func (s RubyCucumberSubstitution) Example() string { + return "bundle exec cucumber {{ scenarios }}" +} + +func (s RubyCucumberSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError( + "Retrying Cucumber requires a template with the 'scenarios' keyword; no keywords were found", + ) + } + + if len(keywords) > 1 { + return errors.NewInputError( + "Retrying Cucumber requires a template with only the 'scenarios' keyword; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if keywords[0] != "scenarios" { + return errors.NewInputError( + "Retrying Cucumber requires a template with only the 'scenarios' keyword; '%v' was found instead", + keywords[0], + ) + } + + return nil +} + +func (s RubyCucumberSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + scenarios := make([]string, 0) + scenariosSeen := map[string]struct{}{} + + for _, test := range testResults.Tests { + if filter(test) { + scenarioLocation := test.Location.File + if test.Location.Line != nil { + scenarioLocation = fmt.Sprintf("%s:%v", test.Location.File, *test.Location.Line) + } + example := templating.ShellEscape(scenarioLocation) + if _, ok := scenariosSeen[example]; ok { + continue + } + + scenarios = append(scenarios, fmt.Sprintf("'%v'", example)) + scenariosSeen[example] = struct{}{} + } + } + + if len(scenarios) > 0 { + return []map[string]string{{"scenarios": strings.Join(scenarios, " ")}}, nil + } + + return []map[string]string{}, nil +} diff --git a/internal/captain/targetedretries/ruby_cucumber_substitution_test.go b/internal/captain/targetedretries/ruby_cucumber_substitution_test.go new file mode 100644 index 0000000..f088aca --- /dev/null +++ b/internal/captain/targetedretries/ruby_cucumber_substitution_test.go @@ -0,0 +1,234 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("RubyCucumberSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.RubyCucumberSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.RubyCucumberSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/cucumber-js.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyCucumberParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["scenarios"] < substitutions[j]["scenarios"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.RubyCucumberSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.RubyCucumberSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bundle exec cucumber") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too many placeholders", func() { + substitution := targetedretries.RubyCucumberSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bundle exec cucumber {{ file }} {{ scenarios }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template without a scenarios placeholder", func() { + substitution := targetedretries.RubyCucumberSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bundle exec cucumber {{ file }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with only a scenarios placeholder", func() { + substitution := targetedretries.RubyCucumberSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bundle exec cucumber {{ scenarios }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns the shell escaped element start identifiers", func() { + compiledTemplate, compileErr := templating.CompileTemplate("bundle exec cucumber {{ scenarios }}") + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "file1 with ' single quotes" + file2 := "file2" + file3 := "file3" + file4 := "file4" + file5 := "file5" + file6 := "file6" + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Location: &v1.Location{File: file3}, + Attempt: v1.TestAttempt{ + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Location: &v1.Location{File: file4}, + Attempt: v1.TestAttempt{ + Status: v1.NewPendedTestStatus(nil), + }, + }, + { + Location: &v1.Location{File: file5}, + Attempt: v1.TestAttempt{ + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Location: &v1.Location{File: file6}, + Attempt: v1.TestAttempt{ + Status: v1.NewSkippedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.RubyCucumberSubstitution{} + Expect(substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + )).To(Equal( + []map[string]string{ + { + "scenarios": `'file1 with '"'"' single quotes' ` + + `'file2' ` + + `'file3'`, + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate("bundle exec cucumber {{ scenarios }}") + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "file1 with ' single quotes" + file2 := "file2" + file3 := "file3" + file4 := "file4" + file5 := "file5" + file6 := "file6" + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + { + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{ + Status: v1.NewCanceledTestStatus(), + }, + }, + { + Location: &v1.Location{File: file3}, + Attempt: v1.TestAttempt{ + Status: v1.NewTimedOutTestStatus(nil, nil, nil), + }, + }, + { + Location: &v1.Location{File: file4}, + Attempt: v1.TestAttempt{ + Status: v1.NewPendedTestStatus(nil), + }, + }, + { + Location: &v1.Location{File: file5}, + Attempt: v1.TestAttempt{ + Status: v1.NewSuccessfulTestStatus(), + }, + }, + { + Location: &v1.Location{File: file6}, + Attempt: v1.TestAttempt{ + Status: v1.NewSkippedTestStatus(nil), + }, + }, + }, + } + + substitution := targetedretries.RubyCucumberSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "scenarios": `'file1 with '"'"' single quotes'`, + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/ruby_minitest_substitution.go b/internal/captain/targetedretries/ruby_minitest_substitution.go new file mode 100644 index 0000000..5d7bb17 --- /dev/null +++ b/internal/captain/targetedretries/ruby_minitest_substitution.go @@ -0,0 +1,141 @@ +package targetedretries + +import ( + "fmt" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type RubyMinitestSubstitution struct{} + +func (s RubyMinitestSubstitution) Example() string { + return "bin/rails test {{ tests }}" +} + +func (s RubyMinitestSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError( + "Retrying minitest requires a template with the 'tests' keyword " + + "or a template with the 'file' and 'name' keywords; " + + "no keywords were found", + ) + } + + if len(keywords) > 2 { + return errors.NewInputError( + "Retrying minitest requires a template with the 'tests' keyword "+ + "or a template with the 'file' and 'name' keywords; "+ + "these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if len(keywords) == 1 { + keyword := keywords[0] + + if keyword != "tests" { + return errors.NewInputError( + "Retrying minitest requires a template with the 'tests' keyword "+ + "or a template with the 'file' and 'name' keywords; "+ + "'%v' was found", + keyword, + ) + } + + return nil + } + + if (keywords[0] == "file" && keywords[1] == "name") || + (keywords[0] == "name" && keywords[1] == "file") { + return nil + } + + return errors.NewInputError( + "Retrying minitest requires a template with the 'tests' keyword "+ + "or a template with the 'file' and 'name' keywords; "+ + "'%v' and '%v were found", + keywords[0], + keywords[1], + ) +} + +func (s RubyMinitestSubstitution) SubstitutionsFor( + compiledTemplate templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 1 { + return s.allTestsSubstitutions(testResults, filter) + } + + return s.singleTestSubstitutions(testResults, filter) +} + +func (s RubyMinitestSubstitution) allTestsSubstitutions( + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + tests := make([]string, 0) + + for _, test := range testResults.Tests { + if filter(test) { + tests = append( + tests, + fmt.Sprintf("'%v:%v'", templating.ShellEscape(test.Location.File), *test.Location.Line), + ) + } + } + + if len(tests) > 0 { + return []map[string]string{{"tests": strings.Join(tests, " ")}}, nil + } + + return []map[string]string{}, nil +} + +func (s RubyMinitestSubstitution) singleTestSubstitutions( + testResults v1.TestResults, + filter func(v1.Test) bool, +) ([]map[string]string, error) { + testsSeenByFile := map[string]map[string]struct{}{} + + for _, test := range testResults.Tests { + if !test.Attempt.Status.ImpliesFailure() { + continue + } + if !filter(test) { + continue + } + + file := templating.ShellEscape(test.Location.File) + if _, ok := testsSeenByFile[file]; !ok { + testsSeenByFile[file] = map[string]struct{}{} + } + + methodName := templating.ShellEscape(test.Lineage[len(test.Lineage)-1]) + if _, ok := testsSeenByFile[file][methodName]; ok { + continue + } + + testsSeenByFile[file][methodName] = struct{}{} + } + + substitutions := make([]map[string]string, 0) + for file, testsSeen := range testsSeenByFile { + for methodName := range testsSeen { + substitutions = append(substitutions, map[string]string{ + "file": file, + "name": methodName, + }) + } + } + + return substitutions, nil +} diff --git a/internal/captain/targetedretries/ruby_minitest_substitution_test.go b/internal/captain/targetedretries/ruby_minitest_substitution_test.go new file mode 100644 index 0000000..951891e --- /dev/null +++ b/internal/captain/targetedretries/ruby_minitest_substitution_test.go @@ -0,0 +1,413 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("RubyMinitestSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.RubyMinitestSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file for Rails", func() { + substitution := targetedretries.RubyMinitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bin/rails test {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/minitest.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyMinitestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + if substitutions[i]["file"] != substitutions[j]["file"] { + return substitutions[i]["file"] < substitutions[j]["file"] + } + + return substitutions[i]["name"] < substitutions[j]["name"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + It("works with a real file for non-Rails", func() { + substitution := targetedretries.RubyMinitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("ruby -I test '{{ file }}' --name '{{ name }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/minitest.xml") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyMinitestParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + if substitutions[i]["file"] != substitutions[j]["file"] { + return substitutions[i]["file"] < substitutions[j]["file"] + } + + return substitutions[i]["name"] < substitutions[j]["name"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.RubyMinitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.RubyMinitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bin/rails test") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too many placeholders", func() { + substitution := targetedretries.RubyMinitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bin/rails test {{ file }} {{ test }} {{ wat }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with one placeholder that isn't tests", func() { + substitution := targetedretries.RubyMinitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bin/rails test {{ file }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with one placeholder that is tests", func() { + substitution := targetedretries.RubyMinitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bin/rails test {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + + It("is invalid for a template with two placeholder that are both incorrect", func() { + substitution := targetedretries.RubyMinitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("ruby -I test '{{ foo }}' --name '{{ bar }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with two placeholder where one is incorrect", func() { + substitution := targetedretries.RubyMinitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("ruby -I test '{{ file }}' --name '{{ bar }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with a file and name placeholder", func() { + substitution := targetedretries.RubyMinitestSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("ruby -I test '{{ file }}' --name '{{ name }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns the shell escaped test locations for the tests keyword", func() { + compiledTemplate, compileErr := templating.CompileTemplate("bin/rails test {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "/path/to/file with spaces.rb" + file2 := "/path/to/filewith'.rb" + file3 := "/path/to/file.rb" + + line1 := 10 + line2 := 20 + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Location: &v1.Location{File: file1, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Location: &v1.Location{File: file2, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Location: &v1.Location{File: file3, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Location: &v1.Location{File: file1, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Location: &v1.Location{File: file2, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Location: &v1.Location{File: file3, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.RubyMinitestSubstitution{} + Expect(substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + )).To(Equal( + []map[string]string{ + { + "tests": `'/path/to/file with spaces.rb:10' ` + + `'/path/to/filewith'"'"'.rb:10' ` + + `'/path/to/file.rb:20'`, + }, + }, + )) + }) + + It("filters the tests for the tests keyword with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate("bin/rails test {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "/path/to/file with spaces.rb" + file2 := "/path/to/filewith'.rb" + file3 := "/path/to/file.rb" + + line1 := 10 + line2 := 20 + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Location: &v1.Location{File: file1, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Location: &v1.Location{File: file2, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Location: &v1.Location{File: file3, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Location: &v1.Location{File: file1, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Location: &v1.Location{File: file2, Line: &line2}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Location: &v1.Location{File: file3, Line: &line1}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.RubyMinitestSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "tests": "'/path/to/file with spaces.rb:10'", + }, + }, + )) + }) + + It("returns the individual escaped files and names", func() { + compiledTemplate, compileErr := templating.CompileTemplate("ruby -I test '{{ file }}' --name '{{ name }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "/path/to/file with spaces.rb" + file2 := "/path/to/filewith'.rb" + file3 := "/path/to/file.rb" + + lineage1 := []string{"classname", "test_method_name_1"} + lineage2 := []string{"classname", "test_method_name_2"} + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Lineage: lineage1, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage1, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file3}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Lineage: lineage1, + Location: &v1.Location{File: file3}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.RubyMinitestSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["file"] < substitutions[j]["file"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "file": "/path/to/file with spaces.rb", + "name": "test_method_name_1", + }, + { + "file": "/path/to/file.rb", + "name": "test_method_name_2", + }, + { + "file": `/path/to/filewith'"'"'.rb`, + "name": "test_method_name_1", + }, + }, + )) + }) + + It("filters the tests for the file/name keywords with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate("ruby -I test '{{ file }}' --name '{{ name }}'") + Expect(compileErr).NotTo(HaveOccurred()) + + file1 := "/path/to/file with spaces.rb" + file2 := "/path/to/filewith'.rb" + file3 := "/path/to/file.rb" + + lineage1 := []string{"classname", "test_method_name_1"} + lineage2 := []string{"classname", "test_method_name_2"} + + testResults := v1.TestResults{ + Tests: []v1.Test{ + { + Lineage: lineage1, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage1, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file3}, + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file1}, + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + }, + { + Lineage: lineage2, + Location: &v1.Location{File: file2}, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Lineage: lineage1, + Location: &v1.Location{File: file3}, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + substitution := targetedretries.RubyMinitestSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["file"] < substitutions[j]["file"] + }) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "file": "/path/to/file with spaces.rb", + "name": "test_method_name_1", + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/ruby_rspec_substitution.go b/internal/captain/targetedretries/ruby_rspec_substitution.go new file mode 100644 index 0000000..ea98324 --- /dev/null +++ b/internal/captain/targetedretries/ruby_rspec_substitution.go @@ -0,0 +1,63 @@ +package targetedretries + +import ( + "fmt" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type RubyRSpecSubstitution struct{} + +func (s RubyRSpecSubstitution) Example() string { + return "bundle exec rspec {{ tests }}" +} + +func (s RubyRSpecSubstitution) ValidateTemplate(compiledTemplate templating.CompiledTemplate) error { + keywords := compiledTemplate.Keywords() + + if len(keywords) == 0 { + return errors.NewInputError("Retrying RSpec requires a template with the 'tests' keyword; no keywords were found") + } + + if len(keywords) > 1 { + return errors.NewInputError( + "Retrying RSpec requires a template with only the 'tests' keyword; these were found: %v", + strings.Join(keywords, ", "), + ) + } + + if keywords[0] != "tests" { + return errors.NewInputError( + "Retrying RSpec requires a template with only the 'tests' keyword; '%v' was found instead", + keywords[0], + ) + } + + return nil +} + +func (s RubyRSpecSubstitution) SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(test v1.Test) bool, +) ([]map[string]string, error) { + testIdentifiers := make([]string, 0) + + for _, test := range testResults.Tests { + if filter(test) { + testIdentifiers = append( + testIdentifiers, + fmt.Sprintf("'%v'", templating.ShellEscape(*test.ID)), + ) + } + } + + if len(testIdentifiers) > 0 { + return []map[string]string{{"tests": strings.Join(testIdentifiers, " ")}}, nil + } + + return []map[string]string{}, nil +} diff --git a/internal/captain/targetedretries/ruby_rspec_substitution_test.go b/internal/captain/targetedretries/ruby_rspec_substitution_test.go new file mode 100644 index 0000000..c54b50b --- /dev/null +++ b/internal/captain/targetedretries/ruby_rspec_substitution_test.go @@ -0,0 +1,173 @@ +package targetedretries_test + +import ( + "os" + "sort" + + "github.com/bradleyjkemp/cupaloy" + + "github.com/rwx-cloud/cli/internal/captain/parsing" + "github.com/rwx-cloud/cli/internal/captain/targetedretries" + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("RubyRSpecSubstitution", func() { + It("adheres to the Substitution interface", func() { + var substitution targetedretries.Substitution = targetedretries.RubyRSpecSubstitution{} + Expect(substitution).NotTo(BeNil()) + }) + + It("works with a real file", func() { + substitution := targetedretries.RubyRSpecSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + + fixture, err := os.Open("../../test/fixtures/rspec.json") + Expect(err).ToNot(HaveOccurred()) + + testResults, err := parsing.RubyRSpecParser{}.Parse(fixture) + Expect(err).ToNot(HaveOccurred()) + + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + *testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + ) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(substitutions, func(i int, j int) bool { + return substitutions[i]["tests"] < substitutions[j]["tests"] + }) + cupaloy.SnapshotT(GinkgoT(), substitutions) + }) + + Describe("Example", func() { + It("compiles and is valid", func() { + substitution := targetedretries.RubyRSpecSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate(substitution.Example()) + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("ValidateTemplate", func() { + It("is invalid for a template without placeholders", func() { + substitution := targetedretries.RubyRSpecSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bundle exec rspec") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template with too many placeholders", func() { + substitution := targetedretries.RubyRSpecSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bundle exec rspec {{ file }} {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is invalid for a template without a tests placeholder", func() { + substitution := targetedretries.RubyRSpecSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bundle exec rspec {{ file }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).To(HaveOccurred()) + }) + + It("is valid for a template with only a tests placeholder", func() { + substitution := targetedretries.RubyRSpecSubstitution{} + compiledTemplate, compileErr := templating.CompileTemplate("bundle exec rspec {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + err := substitution.ValidateTemplate(compiledTemplate) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe("Substitutions", func() { + It("returns the shell escaped test identifiers", func() { + compiledTemplate, compileErr := templating.CompileTemplate("bundle exec rspec {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + id1 := "/path/to/file with spaces.rb:10" + id2 := "/path/to/filewith'.rb:15" + id3 := "/path/to/otherfile.rb:[1:2:2:5]" + id4 := "/path/to/otherfile.rb:[1:3:2:5]" + id5 := "/path/to/otherfile.rb:[1:4:2:5]" + id6 := "/path/to/otherfile.rb:[1:5:2:5]" + testResults := v1.TestResults{ + Tests: []v1.Test{ + {ID: &id1, Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + {ID: &id2, Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}}, + {ID: &id3, Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}}, + {ID: &id4, Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}}, + {ID: &id5, Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}}, + {ID: &id6, Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}}, + }, + } + + substitution := targetedretries.RubyRSpecSubstitution{} + Expect(substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(t v1.Test) bool { return t.Attempt.Status.ImpliesFailure() }, + )).To(Equal( + []map[string]string{ + { + "tests": `'/path/to/file with spaces.rb:10' '/path/to/filewith'"'"'.rb:15' ` + + `'/path/to/otherfile.rb:[1:2:2:5]'`, + }, + }, + )) + }) + + It("filters the tests with the provided function", func() { + compiledTemplate, compileErr := templating.CompileTemplate("bundle exec rspec {{ tests }}") + Expect(compileErr).NotTo(HaveOccurred()) + + id1 := "/path/to/file with spaces.rb:10" + id2 := "/path/to/filewith'.rb:15" + id3 := "/path/to/otherfile.rb:[1:2:2:5]" + id4 := "/path/to/otherfile.rb:[1:3:2:5]" + id5 := "/path/to/otherfile.rb:[1:4:2:5]" + id6 := "/path/to/otherfile.rb:[1:5:2:5]" + testResults := v1.TestResults{ + Tests: []v1.Test{ + {ID: &id1, Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + {ID: &id2, Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}}, + {ID: &id3, Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}}, + {ID: &id4, Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}}, + {ID: &id5, Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}}, + {ID: &id6, Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}}, + }, + } + + substitution := targetedretries.RubyRSpecSubstitution{} + substitutions, err := substitution.SubstitutionsFor( + compiledTemplate, + testResults, + func(test v1.Test) bool { return test.Attempt.Status.Kind == v1.TestStatusFailed }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(substitutions).To(Equal( + []map[string]string{ + { + "tests": "'/path/to/file with spaces.rb:10'", + }, + }, + )) + }) + }) +}) diff --git a/internal/captain/targetedretries/substitution.go b/internal/captain/targetedretries/substitution.go new file mode 100644 index 0000000..4adbe38 --- /dev/null +++ b/internal/captain/targetedretries/substitution.go @@ -0,0 +1,36 @@ +package targetedretries + +import ( + "github.com/rwx-cloud/cli/internal/captain/templating" + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" +) + +type Substitution interface { + Example() string + ValidateTemplate(compiledTemplate templating.CompiledTemplate) error + SubstitutionsFor( + _ templating.CompiledTemplate, + testResults v1.TestResults, + filter func(v1.Test) bool, + ) ([]map[string]string, error) +} + +var SubstitutionsByFramework = map[v1.Framework]Substitution{ + v1.DotNetxUnitFramework: new(DotNetxUnitSubstitution), + v1.ElixirExUnitFramework: new(ElixirExUnitSubstitution), + v1.GoGinkgoFramework: new(GoGinkgoSubstitution), + v1.GoTestFramework: new(GoTestSubstitution), + v1.JavaScriptCucumberFramework: new(JavaScriptCucumberSubstitution), + v1.JavaScriptCypressFramework: new(JavaScriptCypressSubstitution), + v1.JavaScriptJestFramework: new(JavaScriptJestSubstitution), + v1.JavaScriptMochaFramework: new(JavaScriptMochaSubstitution), + v1.JavaScriptPlaywrightFramework: new(JavaScriptPlaywrightSubstitution), + v1.JavaScriptVitestFramework: new(JavaScriptVitestSubstitution), + v1.JavaScriptBunFramework: new(JavaScriptBunSubstitution), + v1.PHPUnitFramework: new(PHPUnitSubstitution), + v1.PythonPytestFramework: new(PythonPytestSubstitution), + v1.PythonUnitTestFramework: new(PythonUnitTestSubstitution), + v1.RubyCucumberFramework: new(RubyCucumberSubstitution), + v1.RubyMinitestFramework: new(RubyMinitestSubstitution), + v1.RubyRSpecFramework: new(RubyRSpecSubstitution), +} diff --git a/internal/captain/targetedretries/substitution_test.go b/internal/captain/targetedretries/substitution_test.go new file mode 100644 index 0000000..214f577 --- /dev/null +++ b/internal/captain/targetedretries/substitution_test.go @@ -0,0 +1,101 @@ +package targetedretries_test + +import ( + "github.com/rwx-cloud/cli/internal/captain/templating" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var substitutions = map[string]string{ + "keyword1": "one", + "keyword2": "two", + "keyword3": "three", + "keyword4": "four", +} + +var _ = Describe("CompileTemplate", func() { + It("errs when the same placeholder is specified multiple times", func() { + template := "some-command '{{ keyword1 }}' '{{ keyword1 }}'" + _, err := templating.CompileTemplate(template) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring( + "template requested duplicate substitution of placeholder 'keyword1'", + )) + }) + + It("compiles with no mapping when there are no placeholders", func() { + template := "some-command" + compiledTemplate, err := templating.CompileTemplate(template) + + Expect(err).NotTo(HaveOccurred()) + Expect(compiledTemplate.Keywords()).To(Equal([]string{})) + Expect(compiledTemplate.PlaceholderToKeyword).To(BeEmpty()) + Expect(compiledTemplate.Template).To(Equal(template)) + }) + + It("compiles one placeholder", func() { + template := "some-command '{{keyword1}}}'" + compiledTemplate, err := templating.CompileTemplate(template) + + Expect(err).NotTo(HaveOccurred()) + Expect(compiledTemplate.Keywords()).To(ConsistOf("keyword1")) + Expect(compiledTemplate.PlaceholderToKeyword).To(Equal(map[string]string{"{{keyword1}}": "keyword1"})) + Expect(compiledTemplate.Template).To(Equal(template)) + }) + + It("compiles multiple placeholder", func() { + template := "some-command '{{keyword1}}' '{{ keyword2 }}' '{{keyword3 }}' '{{ keyword4}}'" + compiledTemplate, err := templating.CompileTemplate(template) + + Expect(err).NotTo(HaveOccurred()) + Expect(compiledTemplate.Keywords()).To(ConsistOf("keyword1", "keyword2", "keyword3", "keyword4")) + Expect(compiledTemplate.PlaceholderToKeyword).To(Equal( + map[string]string{ + "{{keyword1}}": "keyword1", + "{{ keyword2 }}": "keyword2", + "{{keyword3 }}": "keyword3", + "{{ keyword4}}": "keyword4", + }, + )) + Expect(compiledTemplate.Template).To(Equal(template)) + }) +}) + +var _ = Describe("Substitute", func() { + It("returns the template when no substitutions are needed", func() { + compiledTemplate := templating.CompiledTemplate{ + Template: "some-command", + PlaceholderToKeyword: map[string]string{}, + } + substituted := compiledTemplate.Substitute(substitutions) + + Expect(substituted).To(Equal("some-command")) + }) + + It("can substitute just once", func() { + compiledTemplate := templating.CompiledTemplate{ + Template: "some-command '{{ keyword1 }}'", + PlaceholderToKeyword: map[string]string{ + "{{ keyword1 }}": "keyword1", + }, + } + substituted := compiledTemplate.Substitute(substitutions) + + Expect(substituted).To(Equal("some-command 'one'")) + }) + + It("can substitute multiple times", func() { + compiledTemplate := templating.CompiledTemplate{ + Template: "some-command '{{ keyword1 }}' '{{keyword2}}'", + PlaceholderToKeyword: map[string]string{ + "{{ keyword1 }}": "keyword1", + "{{keyword2}}": "keyword2", + }, + } + substituted := compiledTemplate.Substitute(substitutions) + + Expect(substituted).To(Equal("some-command 'one' 'two'")) + }) +}) diff --git a/internal/captain/targetedretries/targetedretries_suite_test.go b/internal/captain/targetedretries/targetedretries_suite_test.go new file mode 100644 index 0000000..56c7749 --- /dev/null +++ b/internal/captain/targetedretries/targetedretries_suite_test.go @@ -0,0 +1,15 @@ +package targetedretries_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestTargetedRetries(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "Targeted Retries Suite") +} diff --git a/internal/captain/templating/compiled_template.go b/internal/captain/templating/compiled_template.go new file mode 100644 index 0000000..395254c --- /dev/null +++ b/internal/captain/templating/compiled_template.go @@ -0,0 +1,69 @@ +package templating + +import ( + "regexp" + "strings" + + "github.com/rwx-cloud/cli/internal/captain/errors" +) + +var ( + placeholderRegexp = regexp.MustCompile(`({{\s?\w+\s?}})`) + keywordRegexp = regexp.MustCompile(`^{{\s?(\w+)\s?}}$`) +) + +type CompiledTemplate struct { + Template string + PlaceholderToKeyword map[string]string +} + +func CompileTemplate(template string) (CompiledTemplate, error) { + placeholders := placeholderRegexp.FindAllString(template, -1) + if len(placeholders) == 0 { + return CompiledTemplate{Template: template, PlaceholderToKeyword: map[string]string{}}, nil + } + + keywordsSubstituted := make(map[string]struct{}, len(placeholders)) + placeholderToKeyword := make(map[string]string, len(placeholders)) + for _, placeholder := range placeholders { + submatches := keywordRegexp.FindStringSubmatch(placeholder) + if len(submatches) != 2 { + return CompiledTemplate{}, errors.NewInputError( + "template included a malformed placeholder '%v'", + placeholder, + ) + } + + keyword := submatches[1] + if _, ok := keywordsSubstituted[keyword]; ok { + return CompiledTemplate{}, errors.NewInputError( + "template requested duplicate substitution of placeholder '%v'", + keyword, + ) + } + keywordsSubstituted[keyword] = struct{}{} + placeholderToKeyword[placeholder] = keyword + } + + return CompiledTemplate{Template: template, PlaceholderToKeyword: placeholderToKeyword}, nil +} + +func (ct CompiledTemplate) Keywords() []string { + keywords := make([]string, len(ct.PlaceholderToKeyword)) + + i := 0 + for _, keyword := range ct.PlaceholderToKeyword { + keywords[i] = keyword + i++ + } + + return keywords +} + +func (ct CompiledTemplate) Substitute(substitutionLookup map[string]string) string { + substituted := ct.Template + for placeholder, keyword := range ct.PlaceholderToKeyword { + substituted = strings.Replace(substituted, placeholder, substitutionLookup[keyword], 1) + } + return substituted +} diff --git a/internal/captain/templating/escaping.go b/internal/captain/templating/escaping.go new file mode 100644 index 0000000..8910cdf --- /dev/null +++ b/internal/captain/templating/escaping.go @@ -0,0 +1,14 @@ +package templating + +import ( + "regexp" + "strings" +) + +func ShellEscape(value string) string { + return strings.ReplaceAll(value, "'", `'"'"'`) +} + +func RegexpEscape(value string) string { + return regexp.QuoteMeta(value) +} diff --git a/internal/captain/templating/escaping_test.go b/internal/captain/templating/escaping_test.go new file mode 100644 index 0000000..e6108f0 --- /dev/null +++ b/internal/captain/templating/escaping_test.go @@ -0,0 +1,28 @@ +package templating_test + +import ( + "github.com/rwx-cloud/cli/internal/captain/templating" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("ShellEscape", func() { + It("ignores values without single quotes", func() { + Expect(templating.ShellEscape("some value")).To(Equal("some value")) + }) + + It("escapes single quotes, no matter where they are", func() { + Expect(templating.ShellEscape("'some ' val'ue'")).To(Equal( + `'"'"'some '"'"' val'"'"'ue'"'"'`, + )) + }) +}) + +var _ = Describe("RegexpEscape", func() { + It("escapes regexp meta characters", func() { + Expect(templating.RegexpEscape("a test with a + and a .")).To(Equal( + `a test with a \+ and a \.`, + )) + }) +}) diff --git a/internal/captain/templating/templating_suite_test.go b/internal/captain/templating/templating_suite_test.go new file mode 100644 index 0000000..2ff0ee1 --- /dev/null +++ b/internal/captain/templating/templating_suite_test.go @@ -0,0 +1,15 @@ +package templating_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestTemplating(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "Templating Suite") +} diff --git a/internal/captain/testing/test_file_timing.go b/internal/captain/testing/test_file_timing.go new file mode 100644 index 0000000..fcfc2fd --- /dev/null +++ b/internal/captain/testing/test_file_timing.go @@ -0,0 +1,32 @@ +package testing + +import ( + "fmt" + "time" +) + +// TestFileTiming is an estimated runtime duration for a test file based off of historical runs recorded by Captain +type TestFileTiming struct { + Filepath string `json:"file_path"` + Duration time.Duration `json:"duration_in_nanoseconds"` +} + +func (t TestFileTiming) String() string { + return fmt.Sprintf("'%s' (%s)", t.Filepath, t.Duration) +} + +// FileTimingMatch represents the client file path matching the server test file timing. +// We store the client file path alongside the timing so we can ensure to only run the file paths +// originally provided by the client. +type FileTimingMatch struct { + FileTiming TestFileTiming + ClientFilepath string +} + +func (m FileTimingMatch) String() string { + return fmt.Sprintf("'%s' (%s)", m.ClientFilepath, m.FileTiming.Duration) +} + +func (m FileTimingMatch) Duration() time.Duration { + return m.FileTiming.Duration +} diff --git a/internal/captain/testing/test_partition.go b/internal/captain/testing/test_partition.go new file mode 100644 index 0000000..55c9970 --- /dev/null +++ b/internal/captain/testing/test_partition.go @@ -0,0 +1,27 @@ +package testing + +import ( + "fmt" + "time" +) + +type TestPartition struct { + Index int + Runtime time.Duration + TestFilePaths []string +} + +func (p TestPartition) Add(matchedTiming FileTimingMatch) TestPartition { + p = p.AddFilePath(matchedTiming.ClientFilepath) + p.Runtime += matchedTiming.Duration() + return p +} + +func (p TestPartition) AddFilePath(filepath string) TestPartition { + p.TestFilePaths = append(p.TestFilePaths, filepath) + return p +} + +func (p TestPartition) String() string { + return fmt.Sprintf("[PART %d (%0.2fs)]", p.Index, p.Runtime.Seconds()) +} diff --git a/internal/captain/testing/test_partition_test.go b/internal/captain/testing/test_partition_test.go new file mode 100644 index 0000000..7defbbe --- /dev/null +++ b/internal/captain/testing/test_partition_test.go @@ -0,0 +1,47 @@ +package testing_test + +import ( + "time" + + "github.com/rwx-cloud/cli/internal/captain/testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("TestPartition.Add", func() { + It("appends matching test file path and updates the runtime", func() { + partition := testing.TestPartition{ + Index: 0, + TestFilePaths: []string{}, + Runtime: time.Duration(10), + } + testFileTiming := testing.TestFileTiming{ + Filepath: "./spec/a_spec.rb", + Duration: time.Duration(2), + } + fileTimingMatch := testing.FileTimingMatch{ + FileTiming: testFileTiming, + ClientFilepath: "spec/a_spec.rb", + } + partition = partition.Add(fileTimingMatch) + + Expect(partition.Runtime).To(Equal(time.Duration(12))) + Expect(partition.TestFilePaths).To(Equal([]string{"spec/a_spec.rb"})) + }) +}) + +var _ = Describe("TestPartition.AddFilePath", func() { + It("appends filepath to the but doesn't update the runtime", func() { + clientTestFilepath := "spec/a_spec.rb" + partition := testing.TestPartition{ + Index: 0, + TestFilePaths: []string{}, + Runtime: time.Duration(10), + } + partition = partition.AddFilePath(clientTestFilepath) + + Expect(partition.Runtime).To(Equal(time.Duration(10))) + Expect(partition.TestFilePaths).To(Equal([]string{clientTestFilepath})) + }) +}) diff --git a/internal/captain/testing/testing_suite_test.go b/internal/captain/testing/testing_suite_test.go new file mode 100644 index 0000000..1464d19 --- /dev/null +++ b/internal/captain/testing/testing_suite_test.go @@ -0,0 +1,15 @@ +package testing_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestTesting(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "Testing Suite") +} diff --git a/internal/captain/testingschema/v1/framework.go b/internal/captain/testingschema/v1/framework.go new file mode 100644 index 0000000..34c3723 --- /dev/null +++ b/internal/captain/testingschema/v1/framework.go @@ -0,0 +1,172 @@ +package v1 + +import ( + "fmt" + "strings" +) + +type FrameworkLanguage string + +type FrameworkKind string + +const ( + FrameworkKindCucumber FrameworkKind = "Cucumber" + FrameworkKindCypress FrameworkKind = "Cypress" + FrameworkKindExUnit FrameworkKind = "ExUnit" + FrameworkKindGinkgo FrameworkKind = "Ginkgo" + FrameworkKindGoTest FrameworkKind = "go test" + FrameworkKindJest FrameworkKind = "Jest" + FrameworkKindKarma FrameworkKind = "Karma" + FrameworkKindMinitest FrameworkKind = "minitest" + FrameworkKindMocha FrameworkKind = "Mocha" + FrameworkKindPHPUnit FrameworkKind = "PHPUnit" + FrameworkKindPlaywright FrameworkKind = "Playwright" + FrameworkKindPytest FrameworkKind = "pytest" + FrameworkKindUnitTest FrameworkKind = "unittest" + FrameworkKindRSpec FrameworkKind = "RSpec" + FrameworkKindxUnit FrameworkKind = "xUnit" + FrameworkKindVitest FrameworkKind = "Vitest" + FrameworkKindBun FrameworkKind = "Bun" + + FrameworkLanguageDotNet FrameworkLanguage = ".NET" + FrameworkLanguageElixir FrameworkLanguage = "Elixir" + FrameworkLanguageGo FrameworkLanguage = "Go" + FrameworkLanguageJavaScript FrameworkLanguage = "JavaScript" + FrameworkLanguagePHP FrameworkLanguage = "PHP" + FrameworkLanguagePython FrameworkLanguage = "Python" + FrameworkLanguageRuby FrameworkLanguage = "Ruby" + + FrameworkKindOther FrameworkKind = "other" + FrameworkLanguageOther FrameworkLanguage = "other" +) + +type Framework struct { + Language FrameworkLanguage `json:"language"` + Kind FrameworkKind `json:"kind"` + ProvidedLanguage *string `json:"providedLanguage,omitempty"` + ProvidedKind *string `json:"providedKind,omitempty"` +} + +var KnownFrameworks []Framework + +func registerFramework(framework Framework) Framework { + KnownFrameworks = append(KnownFrameworks, framework) + return framework +} + +var ( + DotNetxUnitFramework = registerFramework( + Framework{Language: FrameworkLanguageDotNet, Kind: FrameworkKindxUnit}, + ) + ElixirExUnitFramework = registerFramework( + Framework{Language: FrameworkLanguageElixir, Kind: FrameworkKindExUnit}, + ) + GoGinkgoFramework = registerFramework( + Framework{Language: FrameworkLanguageGo, Kind: FrameworkKindGinkgo}, + ) + GoTestFramework = registerFramework( + Framework{Language: FrameworkLanguageGo, Kind: FrameworkKindGoTest}, + ) + JavaScriptCucumberFramework = registerFramework( + Framework{Language: FrameworkLanguageJavaScript, Kind: FrameworkKindCucumber}, + ) + JavaScriptCypressFramework = registerFramework( + Framework{Language: FrameworkLanguageJavaScript, Kind: FrameworkKindCypress}, + ) + JavaScriptJestFramework = registerFramework( + Framework{Language: FrameworkLanguageJavaScript, Kind: FrameworkKindJest}, + ) + JavaScriptKarmaFramework = registerFramework( + Framework{Language: FrameworkLanguageJavaScript, Kind: FrameworkKindKarma}, + ) + JavaScriptMochaFramework = registerFramework( + Framework{Language: FrameworkLanguageJavaScript, Kind: FrameworkKindMocha}, + ) + JavaScriptPlaywrightFramework = registerFramework( + Framework{Language: FrameworkLanguageJavaScript, Kind: FrameworkKindPlaywright}, + ) + JavaScriptVitestFramework = registerFramework( + Framework{Language: FrameworkLanguageJavaScript, Kind: FrameworkKindVitest}, + ) + JavaScriptBunFramework = registerFramework( + Framework{Language: FrameworkLanguageJavaScript, Kind: FrameworkKindBun}, + ) + PHPUnitFramework = registerFramework( + Framework{Language: FrameworkLanguagePHP, Kind: FrameworkKindPHPUnit}, + ) + PythonPytestFramework = registerFramework( + Framework{Language: FrameworkLanguagePython, Kind: FrameworkKindPytest}, + ) + PythonUnitTestFramework = registerFramework( + Framework{Language: FrameworkLanguagePython, Kind: FrameworkKindUnitTest}, + ) + RubyCucumberFramework = registerFramework( + Framework{Language: FrameworkLanguageRuby, Kind: FrameworkKindCucumber}, + ) + RubyMinitestFramework = registerFramework( + Framework{Language: FrameworkLanguageRuby, Kind: FrameworkKindMinitest}, + ) + RubyRSpecFramework = registerFramework( + Framework{Language: FrameworkLanguageRuby, Kind: FrameworkKindRSpec}, + ) +) + +func NewOtherFramework(providedLanguage *string, providedKind *string) Framework { + return Framework{ + Language: FrameworkLanguageOther, + Kind: FrameworkKindOther, + ProvidedLanguage: providedLanguage, + ProvidedKind: providedKind, + } +} + +func CoerceFramework(providedLanguage string, providedKind string) Framework { + framework := NewOtherFramework(&providedLanguage, &providedKind) + + for _, knownFramework := range KnownFrameworks { + if !strings.EqualFold(string(knownFramework.Language), strings.TrimSpace(providedLanguage)) { + continue + } + + if !strings.EqualFold(string(knownFramework.Kind), strings.TrimSpace(providedKind)) { + continue + } + + framework = knownFramework + break + } + + return framework +} + +func (f Framework) Equal(f2 Framework) bool { + if f.IsOther() && f2.IsOther() { + if f.IsProvided() && f2.IsProvided() { + return *f.ProvidedLanguage == *f2.ProvidedLanguage && *f.ProvidedKind == *f2.ProvidedKind + } + + return !f.IsProvided() && !f2.IsProvided() + } + + return !f.IsOther() && !f2.IsOther() && f.Kind == f2.Kind && f.Language == f2.Language +} + +func (f Framework) IsOther() bool { + return f.Language == FrameworkLanguageOther && f.Kind == FrameworkKindOther +} + +func (f Framework) IsProvided() bool { + return f.ProvidedLanguage != nil && f.ProvidedKind != nil +} + +func (f Framework) String() string { + if f.IsOther() { + if f.IsProvided() { + return fmt.Sprintf("Other: %v (%v)", *f.ProvidedKind, *f.ProvidedLanguage) + } + + return "Other" + } + + return fmt.Sprintf("%v (%v)", f.Kind, f.Language) +} diff --git a/internal/captain/testingschema/v1/framework_test.go b/internal/captain/testingschema/v1/framework_test.go new file mode 100644 index 0000000..ac6c785 --- /dev/null +++ b/internal/captain/testingschema/v1/framework_test.go @@ -0,0 +1,98 @@ +package v1_test + +import ( + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Framework", func() { + Describe("NewOtherFramework", func() { + It("produces a framework of the expected configuration", func() { + providedLanguage := "provided language" + providedKind := "provided kind" + Expect(v1.NewOtherFramework(&providedLanguage, &providedKind)).To(Equal(v1.Framework{ + Language: v1.FrameworkLanguageOther, + Kind: v1.FrameworkKindOther, + ProvidedLanguage: &providedLanguage, + ProvidedKind: &providedKind, + })) + }) + }) + + Describe("IsOther", func() { + It("is true when called for an other framework", func() { + Expect(v1.NewOtherFramework(nil, nil).IsOther()).To(BeTrue()) + }) + + It("is false when called for a known framework", func() { + Expect(v1.RubyRSpecFramework.IsOther()).To(BeFalse()) + }) + }) + + Describe("IsProvided", func() { + It("is true when called for an other framework with all provided fields", func() { + providedLanguage := "provided language" + providedKind := "provided kind" + Expect(v1.NewOtherFramework(&providedLanguage, &providedKind).IsProvided()).To(BeTrue()) + }) + + It("is false when called for an other framework with some or no provided fields", func() { + providedLanguage := "provided language" + providedKind := "provided kind" + Expect(v1.NewOtherFramework(&providedLanguage, nil).IsProvided()).To(BeFalse()) + Expect(v1.NewOtherFramework(nil, &providedKind).IsProvided()).To(BeFalse()) + Expect(v1.NewOtherFramework(nil, nil).IsProvided()).To(BeFalse()) + }) + + It("is false when called for a known framework", func() { + Expect(v1.RubyRSpecFramework.IsProvided()).To(BeFalse()) + }) + }) + + Describe("CoerceFramework", func() { + It("can construct a well-known framework", func() { + providedLanguage := "rUbY" + providedKind := "rspec" + framework := v1.CoerceFramework(providedLanguage, providedKind) + Expect(framework).To(Equal(v1.RubyRSpecFramework)) + }) + + It("cannot mix well-known languages and kinds", func() { + providedLanguage := "rUbY" + providedKind := "mocha" + framework := v1.CoerceFramework(providedLanguage, providedKind) + Expect(framework).To(Equal(v1.NewOtherFramework(&providedLanguage, &providedKind))) + }) + + It("returns other frameworks with their provided kind and language", func() { + providedLanguage := "something" + providedKind := "unknown" + framework := v1.CoerceFramework(providedLanguage, providedKind) + Expect(framework).To(Equal(v1.NewOtherFramework(&providedLanguage, &providedKind))) + }) + }) + + Describe("String", func() { + It("has a good string representation for provided other frameworks", func() { + providedLanguage := "provided language" + providedKind := "provided kind" + Expect(v1.NewOtherFramework(&providedLanguage, &providedKind).String()).To( + Equal("Other: provided kind (provided language)"), + ) + }) + + It("has a good string representation for unprovided other frameworks", func() { + Expect(v1.NewOtherFramework(nil, nil).String()).To( + Equal("Other"), + ) + }) + + It("has a good string representation for known frameworks", func() { + Expect(v1.RubyRSpecFramework.String()).To( + Equal("RSpec (Ruby)"), + ) + }) + }) +}) diff --git a/internal/captain/testingschema/v1/location.go b/internal/captain/testingschema/v1/location.go new file mode 100644 index 0000000..3727414 --- /dev/null +++ b/internal/captain/testingschema/v1/location.go @@ -0,0 +1,17 @@ +package v1 + +import "fmt" + +type Location struct { + File string `json:"file"` + Line *int `json:"line,omitempty"` + Column *int `json:"column,omitempty"` +} + +func (l Location) String() string { + if l.Line != nil { + return fmt.Sprintf("%v:%v", l.File, *l.Line) + } + + return l.File +} diff --git a/internal/captain/testingschema/v1/location_test.go b/internal/captain/testingschema/v1/location_test.go new file mode 100644 index 0000000..e317341 --- /dev/null +++ b/internal/captain/testingschema/v1/location_test.go @@ -0,0 +1,21 @@ +package v1_test + +import ( + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Location", func() { + Describe("String", func() { + It("formats when there is a line number", func() { + line := 15 + Expect(v1.Location{File: "some/file.rb", Line: &line}.String()).To(Equal("some/file.rb:15")) + }) + + It("formats when there is not a line number", func() { + Expect(v1.Location{File: "some/file.rb"}.String()).To(Equal("some/file.rb")) + }) + }) +}) diff --git a/internal/captain/testingschema/v1/merge.go b/internal/captain/testingschema/v1/merge.go new file mode 100644 index 0000000..5bdf2e6 --- /dev/null +++ b/internal/captain/testingschema/v1/merge.go @@ -0,0 +1,88 @@ +package v1 + +// Merges and flattens test results together +func Merge(allTestResults ...[]TestResults) TestResults { + unionedTestResults := make([]TestResults, 0) + for _, batch := range allTestResults { + if results := union(batch); results != nil { + unionedTestResults = append(unionedTestResults, *results) + } + } + + return flatten(unionedTestResults) +} + +func union(separateTestResults []TestResults) *TestResults { + if len(separateTestResults) == 0 { + return nil + } + + unioned, rest := separateTestResults[0], separateTestResults[1:] + for _, testResults := range rest { + unioned.DerivedFrom = append(unioned.DerivedFrom, testResults.DerivedFrom...) + unioned.OtherErrors = append(unioned.OtherErrors, testResults.OtherErrors...) + unioned.Tests = append(unioned.Tests, testResults.Tests...) + } + + unioned.Summary = NewSummary(unioned.Tests, unioned.OtherErrors) + return &unioned +} + +func flatten(unionedTestResults []TestResults) TestResults { + flattened, rest := unionedTestResults[0], unionedTestResults[1:] + flattenedStartedEmpty := len(flattened.Tests) == 0 && + len(flattened.OtherErrors) == 0 && + len(flattened.DerivedFrom) == 0 + + for index, testResults := range rest { + flattened.DerivedFrom = append(flattened.DerivedFrom, testResults.DerivedFrom...) + flattened.OtherErrors = append(flattened.OtherErrors, testResults.OtherErrors...) + + for _, incomingTest := range testResults.Tests { + matchedWithBaseTest := false + + for i, baseTest := range flattened.Tests { + if !baseTest.Matches(incomingTest) { + continue + } + matchedWithBaseTest = true + + newAttempt := incomingTest.Attempt + newPastAttempt := baseTest.Attempt + if newAttempt.Status.ImpliesSkipped() { + // do not flatten skipped statuses into existing tests because they didn't actually run again + break + } + if newAttempt.Status.ImpliesFailure() && !newPastAttempt.Status.ImpliesFailure() { + newAttempt, newPastAttempt = newPastAttempt, newAttempt + } + + pastAttempts := make([]TestAttempt, len(baseTest.PastAttempts)+1) + copy(pastAttempts, baseTest.PastAttempts) + pastAttempts[len(pastAttempts)-1] = newPastAttempt + + flattened.Tests[i] = Test{ + Scope: baseTest.Scope, + ID: baseTest.ID, + Name: baseTest.Name, + Lineage: baseTest.Lineage, + Location: baseTest.Location, + Attempt: newAttempt, + PastAttempts: pastAttempts, + } + break + } + + if !matchedWithBaseTest { + if flattenedStartedEmpty && index == 0 { + flattened.Tests = append(flattened.Tests, incomingTest) + } else { + flattened.Tests = append(flattened.Tests, incomingTest.Tag("missingInPreviousBatchOfResults", true)) + } + } + } + } + + flattened.Summary = NewSummary(flattened.Tests, flattened.OtherErrors) + return flattened +} diff --git a/internal/captain/testingschema/v1/merge_test.go b/internal/captain/testingschema/v1/merge_test.go new file mode 100644 index 0000000..b2f7fcb --- /dev/null +++ b/internal/captain/testingschema/v1/merge_test.go @@ -0,0 +1,679 @@ +package v1_test + +import ( + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Merge", func() { + var ( + rubyRSpec1 *v1.TestResults + rubyRSpec1_2 *v1.TestResults + rubyRSpec2 *v1.TestResults + ) + + BeforeEach(func() { + rubyRSpec1 = &v1.TestResults{ + DerivedFrom: []v1.OriginalTestResults{ + {OriginalFilePath: "path 1"}, + {OriginalFilePath: "path 2"}, + }, + Framework: v1.RubyRSpecFramework, + OtherErrors: []v1.OtherError{ + {Message: "other error 1"}, + }, + Summary: v1.Summary{ + Status: v1.SummaryStatusFailed, + Tests: 3, + OtherErrors: 1, + Successful: 1, + Failed: 1, + Skipped: 1, + }, + Tests: []v1.Test{ + {Name: "test 1", Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}}, + {Name: "test 2", Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + {Name: "test 3", Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}}, + }, + } + rubyRSpec1_2 = &v1.TestResults{ + DerivedFrom: []v1.OriginalTestResults{ + {OriginalFilePath: "path 1", GroupNumber: 2}, + }, + Framework: v1.RubyRSpecFramework, + Summary: v1.Summary{ + Status: v1.SummaryStatusFailed, + Tests: 2, + Successful: 1, + Failed: 1, + }, + Tests: []v1.Test{ + {Name: "test 1", Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + {Name: "test 2", Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}}, + }, + } + rubyRSpec2 = &v1.TestResults{ + DerivedFrom: []v1.OriginalTestResults{ + {OriginalFilePath: "path 3"}, + }, + Framework: v1.RubyRSpecFramework, + OtherErrors: []v1.OtherError{ + {Message: "other error 2"}, + {Message: "other error 3"}, + }, + Summary: v1.Summary{ + Status: v1.SummaryStatusFailed, + Tests: 3, + OtherErrors: 2, + Successful: 1, + Failed: 1, + Skipped: 1, + }, + Tests: []v1.Test{ + {Name: "test 4", Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}}, + {Name: "test 5", Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + {Name: "test 6", Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}}, + }, + } + }) + + It("unions and flattens results", func() { + Expect(v1.Merge( + []v1.TestResults{*rubyRSpec1, *rubyRSpec2}, + []v1.TestResults{*rubyRSpec1_2}, + )).To(Equal( + v1.TestResults{ + DerivedFrom: []v1.OriginalTestResults{ + {OriginalFilePath: "path 1"}, + {OriginalFilePath: "path 2"}, + {OriginalFilePath: "path 3"}, + {OriginalFilePath: "path 1", GroupNumber: 2}, + }, + Framework: v1.RubyRSpecFramework, + OtherErrors: []v1.OtherError{ + {Message: "other error 1"}, + {Message: "other error 2"}, + {Message: "other error 3"}, + }, + Summary: v1.Summary{ + Status: v1.SummaryStatusFailed, + Tests: 6, + Flaky: 2, + OtherErrors: 3, + Successful: 3, + Failed: 1, + Skipped: 2, + Retries: 2, + }, + Tests: []v1.Test{ + { + Name: "test 1", + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + PastAttempts: []v1.TestAttempt{{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + }, + { + Name: "test 2", + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + PastAttempts: []v1.TestAttempt{{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + }, + {Name: "test 3", Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}}, + {Name: "test 4", Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}}, + {Name: "test 5", Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + {Name: "test 6", Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}}, + }, + }, + )) + }) + + It("flattens on all top-level test fields across many batches", func() { + str1 := "1" + str2 := "2" + str3 := "3" + str4 := "4" + str5 := "5" + + int1 := 1 + int2 := 2 + + scope1 := "scope1" + id1 := "id1" + id1other := "id1" + name1 := "name1" + lineage1 := []string{"name", "1"} + location1 := v1.Location{File: "file1.rb", Line: &int1} + location1other := v1.Location{File: "file1.rb", Line: &int1} + + scope2 := "scope2" + id2 := "id2" + name2 := "name2" + lineage2 := []string{"name", "2"} + location2 := v1.Location{File: "file1.rb", Line: &int2} + + results1 := v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str1, nil, nil)}, + }, + { + ID: &id1, + Name: name2, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str2, nil, nil)}, + PastAttempts: []v1.TestAttempt{{Status: v1.NewFailedTestStatus(&str2, nil, nil)}}, + }, + { + ID: &id1, + Name: name1, + Lineage: lineage2, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str3, nil, nil)}, + }, + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str4, nil, nil)}, + }, + { + ID: &id2, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str5, nil, nil)}, + }, + { + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Scope: &scope1, + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Scope: &scope2, + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + }, + } + + results2 := v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &id1other, + Name: name1, + Lineage: lineage2, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str3, nil, nil)}, + }, + { + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + Scope: &scope1, + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + }, + } + + results3 := v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location1other, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + { + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + }, + } + + Expect(v1.Merge([]v1.TestResults{results1}, []v1.TestResults{results2}, []v1.TestResults{results3})).To(Equal( + v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Summary: v1.Summary{ + Status: v1.SummaryStatusFailed, + Tests: 8, + Retries: 5, + Flaky: 2, + Failed: 4, + Successful: 4, + }, + Tests: []v1.Test{ + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + PastAttempts: []v1.TestAttempt{{Status: v1.NewFailedTestStatus(&str1, nil, nil)}}, + }, + { + ID: &id1, + Name: name2, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str2, nil, nil)}, + PastAttempts: []v1.TestAttempt{{Status: v1.NewFailedTestStatus(&str2, nil, nil)}}, + }, + { + ID: &id1, + Name: name1, + Lineage: lineage2, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str3, nil, nil)}, + PastAttempts: []v1.TestAttempt{{Status: v1.NewFailedTestStatus(&str3, nil, nil)}}, + }, + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str4, nil, nil)}, + }, + { + ID: &id2, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str5, nil, nil)}, + }, + { + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + PastAttempts: []v1.TestAttempt{ + {Status: v1.NewSuccessfulTestStatus()}, + {Status: v1.NewCanceledTestStatus()}, + }, + }, + { + Scope: &scope1, + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + PastAttempts: []v1.TestAttempt{ + {Status: v1.NewFailedTestStatus(nil, nil, nil)}, + }, + }, + { + Scope: &scope2, + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + }, + }, + )) + }) + + It("flattens across batches when there are empty batches", func() { + int2 := 2 + + id2 := "id2" + name2 := "name2" + lineage2 := []string{"name", "2"} + location2 := v1.Location{File: "file1.rb", Line: &int2} + + results1 := v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + }, + } + + results2 := v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + }, + } + + results3 := v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + }, + } + + Expect(v1.Merge( + []v1.TestResults{results1}, + []v1.TestResults{}, + []v1.TestResults{results2}, + []v1.TestResults{}, + []v1.TestResults{}, + []v1.TestResults{results3}, + )).To(Equal( + v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Summary: v1.Summary{ + Status: v1.SummaryStatusSuccessful, + Tests: 1, + Retries: 1, + Successful: 1, + }, + Tests: []v1.Test{ + { + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + PastAttempts: []v1.TestAttempt{ + {Status: v1.NewSuccessfulTestStatus()}, + {Status: v1.NewSuccessfulTestStatus()}, + }, + }, + }, + }, + )) + }) + + It("unions any tests found in batches that were not in previous ones", func() { + str1 := "1" + str2 := "2" + + int1 := 1 + int2 := 2 + + id1 := "id1" + name1 := "name1" + lineage1 := []string{"name", "1"} + location1 := v1.Location{File: "file1.rb", Line: &int1} + + id2 := "id2" + name2 := "name2" + lineage2 := []string{"name", "2"} + location2 := v1.Location{File: "file1.rb", Line: &int2} + + results1 := v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str1, nil, nil)}, + }, + }, + } + + results2 := v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &id1, + Name: name1, + Lineage: lineage2, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str2, nil, nil)}, + }, + }, + } + + results3 := v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + }, + } + + Expect(v1.Merge( + []v1.TestResults{results1}, + []v1.TestResults{results2}, + []v1.TestResults{results3}, + )).To(Equal( + v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Summary: v1.Summary{ + Status: v1.SummaryStatusFailed, + Tests: 3, + Failed: 2, + Canceled: 1, + }, + Tests: []v1.Test{ + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str1, nil, nil)}, + }, + { + ID: &id1, + Name: name1, + Lineage: lineage2, + Location: &location1, + Attempt: v1.TestAttempt{ + Status: v1.NewFailedTestStatus(&str2, nil, nil), + Meta: map[string]any{ + "__rwx": map[string]any{ + "missingInPreviousBatchOfResults": true, + }, + }, + }, + }, + { + ID: &id2, + Name: name2, + Lineage: lineage2, + Location: &location2, + Attempt: v1.TestAttempt{ + Status: v1.NewCanceledTestStatus(), + Meta: map[string]any{ + "__rwx": map[string]any{ + "missingInPreviousBatchOfResults": true, + }, + }, + }, + }, + }, + }, + )) + }) + + It("only merges incoming tests into one base test, even if there are multiple matches", func() { + str1 := "1" + + int1 := 1 + + id1 := "id1" + name1 := "name1" + lineage1 := []string{"name", "1"} + location1 := v1.Location{File: "file1.rb", Line: &int1} + + results1 := v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(&str1, nil, nil)}, + }, + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + }, + } + + results2 := v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + }, + } + + Expect(v1.Merge( + []v1.TestResults{results1}, + []v1.TestResults{results2}, + )).To(Equal( + v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Summary: v1.Summary{ + Status: v1.SummaryStatusFailed, + Tests: 2, + Flaky: 1, + Successful: 1, + Canceled: 1, + Retries: 1, + }, + Tests: []v1.Test{ + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + PastAttempts: []v1.TestAttempt{ + {Status: v1.NewFailedTestStatus(&str1, nil, nil)}, + }, + }, + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + }, + }, + }, + )) + }) + + It("does not flatten attempts that didn't actually run", func() { + int1 := 1 + + id1 := "id1" + name1 := "name1" + lineage1 := []string{"name", "1"} + location1 := v1.Location{File: "file1.rb", Line: &int1} + + results1 := v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + }, + } + + results2 := v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Tests: []v1.Test{ + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}, + }, + }, + } + + Expect(v1.Merge( + []v1.TestResults{results1}, + []v1.TestResults{results2}, + )).To(Equal( + v1.TestResults{ + Framework: v1.RubyRSpecFramework, + Summary: v1.Summary{ + Status: v1.SummaryStatusSuccessful, + Tests: 1, + Successful: 1, + }, + Tests: []v1.Test{ + { + ID: &id1, + Name: name1, + Lineage: lineage1, + Location: &location1, + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + }, + }, + }, + )) + }) +}) diff --git a/internal/captain/testingschema/v1/original_test_results.go b/internal/captain/testingschema/v1/original_test_results.go new file mode 100644 index 0000000..a203d3f --- /dev/null +++ b/internal/captain/testingschema/v1/original_test_results.go @@ -0,0 +1,7 @@ +package v1 + +type OriginalTestResults struct { + OriginalFilePath string `json:"originalFilePath"` + Contents string `json:"contents"` + GroupNumber int `json:"groupNumber"` +} diff --git a/internal/captain/testingschema/v1/other_error.go b/internal/captain/testingschema/v1/other_error.go new file mode 100644 index 0000000..ac5d59f --- /dev/null +++ b/internal/captain/testingschema/v1/other_error.go @@ -0,0 +1,9 @@ +package v1 + +type OtherError struct { + Backtrace []string `json:"backtrace,omitempty"` + Exception *string `json:"exception,omitempty"` + Location *Location `json:"location,omitempty"` + Message string `json:"message"` + Meta map[string]any `json:"meta,omitempty"` +} diff --git a/internal/captain/testingschema/v1/strip.go b/internal/captain/testingschema/v1/strip.go new file mode 100644 index 0000000..68eff1d --- /dev/null +++ b/internal/captain/testingschema/v1/strip.go @@ -0,0 +1,81 @@ +package v1 + +import ( + "encoding/base64" + "encoding/json" +) + +const TruncationMessage = "" + +func StripDerivedFrom(testResults TestResults) TestResults { + cleanedDerivedFrom := make([]OriginalTestResults, len(testResults.DerivedFrom)) + + for i, originalTestResults := range testResults.DerivedFrom { + cleanedDerivedFrom[i] = OriginalTestResults{ + OriginalFilePath: originalTestResults.OriginalFilePath, + GroupNumber: originalTestResults.GroupNumber, + Contents: base64.StdEncoding.EncodeToString([]byte(TruncationMessage)), + } + } + + return TestResults{ + Framework: testResults.Framework, + Summary: testResults.Summary, + Tests: testResults.Tests, + OtherErrors: testResults.OtherErrors, + DerivedFrom: cleanedDerivedFrom, + Meta: testResults.Meta, + } +} + +func StripPreviousAttempts(testResults TestResults) TestResults { + for i, test := range testResults.Tests { + for j, attempt := range test.PastAttempts { + testResults.Tests[i].PastAttempts[j].Status = stripStatus(attempt.Status) + } + } + + return testResults +} + +func StripCurrentAttempts(testResults TestResults) TestResults { + for i, test := range testResults.Tests { + if test.Attempt.Status.Backtrace != nil { + testResults.Tests[i].Attempt.Status = stripStatus(test.Attempt.Status) + } + } + + return testResults +} + +func stripStatus(status TestStatus) TestStatus { + if status.Backtrace != nil { + status.Backtrace = []string{TruncationMessage} + } + + if status.OriginalStatus != nil { + s := stripStatus(*status.OriginalStatus) + status.OriginalStatus = &s + } + + return status +} + +func StripToSize(testResults TestResults, fileSizeThresholdBytes int64) TestResults { + type stripFunc func(TestResults) TestResults + for _, strip := range []stripFunc{StripDerivedFrom, StripPreviousAttempts, StripCurrentAttempts} { + // Marshal to check current size + buf, err := json.Marshal(testResults) + if err != nil { + return testResults + } + + if int64(len(buf)) <= fileSizeThresholdBytes { + break + } + + testResults = strip(testResults) + } + + return testResults +} diff --git a/internal/captain/testingschema/v1/summary.go b/internal/captain/testingschema/v1/summary.go new file mode 100644 index 0000000..95ea172 --- /dev/null +++ b/internal/captain/testingschema/v1/summary.go @@ -0,0 +1,102 @@ +package v1 + +import ( + "encoding/json" + + "github.com/rwx-cloud/cli/internal/captain/errors" +) + +type SummaryStatus string + +const ( + SummaryStatusSuccessful SummaryStatus = "successful" + SummaryStatusCanceled SummaryStatus = "canceled" + SummaryStatusFailed SummaryStatus = "failed" + SummaryStatusTimedOut SummaryStatus = "timedOut" +) + +type summaryStatus struct { + Kind string `json:"kind"` +} + +func (ss SummaryStatus) MarshalJSON() ([]byte, error) { + json, err := json.Marshal(&summaryStatus{Kind: (string)(ss)}) + return json, errors.WithStack(err) +} + +func (ss *SummaryStatus) UnmarshalJSON(b []byte) error { + var s summaryStatus + if err := json.Unmarshal(b, &s); err != nil { + return errors.WithStack(err) + } + + *ss = SummaryStatus(s.Kind) + return nil +} + +type Summary struct { + Status SummaryStatus `json:"status"` + Tests int `json:"tests"` + Flaky int `json:"flaky"` + OtherErrors int `json:"otherErrors"` + Retries int `json:"retries"` + Canceled int `json:"canceled"` + Failed int `json:"failed"` + Pended int `json:"pended"` + Quarantined int `json:"quarantined"` + Skipped int `json:"skipped"` + Successful int `json:"successful"` + TimedOut int `json:"timedOut"` + Todo int `json:"todo"` +} + +func NewSummary(tests []Test, otherErrors []OtherError) Summary { + summary := Summary{Tests: len(tests), OtherErrors: len(otherErrors)} + status := SummaryStatusSuccessful + + if len(otherErrors) > 0 { + status = SummaryStatusFailed + } + + for _, test := range tests { + if len(test.PastAttempts) > 0 { + summary.Retries++ + } + + if test.Flaky() { + summary.Flaky++ + } + + if test.Attempt.Status.ImpliesFailure() { + status = SummaryStatusFailed + } + + if test.Attempt.Status.Kind == TestStatusCanceled { + summary.Canceled++ + } + if test.Attempt.Status.Kind == TestStatusFailed { + summary.Failed++ + } + if test.Attempt.Status.Kind == TestStatusPended { + summary.Pended++ + } + if test.Attempt.Status.Kind == TestStatusQuarantined { + summary.Quarantined++ + } + if test.Attempt.Status.Kind == TestStatusSkipped { + summary.Skipped++ + } + if test.Attempt.Status.Kind == TestStatusSuccessful { + summary.Successful++ + } + if test.Attempt.Status.Kind == TestStatusTimedOut { + summary.TimedOut++ + } + if test.Attempt.Status.Kind == TestStatusTodo { + summary.Todo++ + } + } + + summary.Status = status + return summary +} diff --git a/internal/captain/testingschema/v1/summary_test.go b/internal/captain/testingschema/v1/summary_test.go new file mode 100644 index 0000000..1d2b290 --- /dev/null +++ b/internal/captain/testingschema/v1/summary_test.go @@ -0,0 +1,202 @@ +package v1_test + +import ( + "encoding/json" + + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Summary", func() { + Describe("NewSummary", func() { + It("summarizes no tests and no other errors", func() { + Expect(v1.NewSummary(nil, nil)).To(Equal( + v1.Summary{ + Status: v1.SummaryStatusSuccessful, + }, + )) + }) + + It("summarizes test statuses", func() { + var test v1.Test + + test = v1.Test{Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}} + Expect(v1.NewSummary([]v1.Test{test, test}, nil)).To(Equal( + v1.Summary{ + Status: v1.SummaryStatusSuccessful, + Tests: 2, + Successful: 2, + }, + )) + + test = v1.Test{Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}} + Expect(v1.NewSummary([]v1.Test{test, test}, nil)).To(Equal( + v1.Summary{ + Status: v1.SummaryStatusFailed, + Tests: 2, + Failed: 2, + }, + )) + + test = v1.Test{Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}} + Expect(v1.NewSummary([]v1.Test{test, test}, nil)).To(Equal( + v1.Summary{ + Status: v1.SummaryStatusFailed, + Tests: 2, + Canceled: 2, + }, + )) + + test = v1.Test{Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}} + Expect(v1.NewSummary([]v1.Test{test, test}, nil)).To(Equal( + v1.Summary{ + Status: v1.SummaryStatusSuccessful, + Tests: 2, + Pended: 2, + }, + )) + + test = v1.Test{Attempt: v1.TestAttempt{Status: v1.NewSkippedTestStatus(nil)}} + Expect(v1.NewSummary([]v1.Test{test, test}, nil)).To(Equal( + v1.Summary{ + Status: v1.SummaryStatusSuccessful, + Tests: 2, + Skipped: 2, + }, + )) + + test = v1.Test{Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}} + Expect(v1.NewSummary([]v1.Test{test, test}, nil)).To(Equal( + v1.Summary{ + Status: v1.SummaryStatusFailed, + Tests: 2, + TimedOut: 2, + }, + )) + + test = v1.Test{Attempt: v1.TestAttempt{Status: v1.NewTodoTestStatus(nil)}} + Expect(v1.NewSummary([]v1.Test{test, test}, nil)).To(Equal( + v1.Summary{ + Status: v1.SummaryStatusSuccessful, + Tests: 2, + Todo: 2, + }, + )) + + test = v1.Test{Attempt: v1.TestAttempt{ + Status: v1.NewQuarantinedTestStatus(v1.NewFailedTestStatus(nil, nil, nil)), + }} + Expect(v1.NewSummary([]v1.Test{test, test}, nil)).To(Equal( + v1.Summary{ + Status: v1.SummaryStatusSuccessful, + Tests: 2, + Quarantined: 2, + }, + )) + }) + + It("summarizes other errors", func() { + Expect(v1.NewSummary(nil, []v1.OtherError{{Message: "other error"}, {Message: "other error 2"}})).To(Equal( + v1.Summary{ + Status: v1.SummaryStatusFailed, + OtherErrors: 2, + }, + )) + }) + + It("summarizes retries", func() { + test := v1.Test{ + Attempt: v1.TestAttempt{ + Status: v1.NewSuccessfulTestStatus(), + }, + PastAttempts: []v1.TestAttempt{ + { + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + { + Status: v1.NewFailedTestStatus(nil, nil, nil), + }, + }, + } + Expect(v1.NewSummary([]v1.Test{test, test}, nil)).To(Equal( + v1.Summary{ + Status: v1.SummaryStatusSuccessful, + Tests: 2, + Retries: 2, + Flaky: 2, + Successful: 2, + }, + )) + }) + }) + + Describe("SummaryStatus", func() { + It("marshals the summary statuses", func() { + var data []byte + var err error + + data, err = json.Marshal(v1.SummaryStatusSuccessful) + + Expect(err).To(BeNil()) + Expect(string(data)).To(Equal(`{"kind":"successful"}`)) + + data, err = json.Marshal(v1.SummaryStatusFailed) + + Expect(err).To(BeNil()) + Expect(string(data)).To(Equal(`{"kind":"failed"}`)) + + data, err = json.Marshal(v1.SummaryStatusCanceled) + + Expect(err).To(BeNil()) + Expect(string(data)).To(Equal(`{"kind":"canceled"}`)) + + data, err = json.Marshal(v1.SummaryStatusTimedOut) + + Expect(err).To(BeNil()) + Expect(string(data)).To(Equal(`{"kind":"timedOut"}`)) + + data, err = json.Marshal(v1.SummaryStatus("foo")) + + Expect(err).To(BeNil()) + Expect(string(data)).To(Equal(`{"kind":"foo"}`)) + }) + + It("unmarshals the summary statuses", func() { + var ss v1.SummaryStatus + var err error + var data string + + data = `{"kind":"successful"}` + err = json.Unmarshal([]byte(data), &ss) + + Expect(err).To(BeNil()) + Expect(ss).To(Equal(v1.SummaryStatusSuccessful)) + + data = `{"kind":"failed"}` + err = json.Unmarshal([]byte(data), &ss) + + Expect(err).To(BeNil()) + Expect(ss).To(Equal(v1.SummaryStatusFailed)) + + data = `{"kind":"canceled"}` + err = json.Unmarshal([]byte(data), &ss) + + Expect(err).To(BeNil()) + Expect(ss).To(Equal(v1.SummaryStatusCanceled)) + + data = `{"kind":"timedOut"}` + err = json.Unmarshal([]byte(data), &ss) + + Expect(err).To(BeNil()) + Expect(ss).To(Equal(v1.SummaryStatusTimedOut)) + + data = `{"kind":"foo"}` + err = json.Unmarshal([]byte(data), &ss) + + Expect(err).To(BeNil()) + Expect(ss).To(Equal(v1.SummaryStatus("foo"))) + }) + }) +}) diff --git a/internal/captain/testingschema/v1/test.go b/internal/captain/testingschema/v1/test.go new file mode 100644 index 0000000..96caece --- /dev/null +++ b/internal/captain/testingschema/v1/test.go @@ -0,0 +1,309 @@ +package v1 + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/rwx-cloud/cli/internal/captain/errors" +) + +type TestStatusKind string + +const ( + // successful + TestStatusSuccessful TestStatusKind = "successful" + + // failed, but ignored + TestStatusQuarantined TestStatusKind = "quarantined" + + // failures + TestStatusCanceled TestStatusKind = "canceled" + TestStatusFailed TestStatusKind = "failed" + TestStatusTimedOut TestStatusKind = "timedOut" + + // skipped + TestStatusPended TestStatusKind = "pended" + TestStatusSkipped TestStatusKind = "skipped" + TestStatusTodo TestStatusKind = "todo" +) + +type TestIdentityRecipe struct { + Components []string + Strict bool +} + +type TestStatus struct { + Kind TestStatusKind `json:"kind"` + OriginalStatus *TestStatus `json:"originalStatus,omitempty"` + Message *string `json:"message,omitempty"` + Exception *string `json:"exception,omitempty"` + Backtrace []string `json:"backtrace,omitempty"` +} + +func NewCanceledTestStatus() TestStatus { + return TestStatus{Kind: TestStatusCanceled} +} + +func NewFailedTestStatus(message *string, exception *string, backtrace []string) TestStatus { + return TestStatus{ + Kind: TestStatusFailed, + Message: message, + Exception: exception, + Backtrace: backtrace, + } +} + +func NewPendedTestStatus(message *string) TestStatus { + return TestStatus{Kind: TestStatusPended, Message: message} +} + +func NewSkippedTestStatus(message *string) TestStatus { + return TestStatus{Kind: TestStatusSkipped, Message: message} +} + +func NewSuccessfulTestStatus() TestStatus { + return TestStatus{Kind: TestStatusSuccessful} +} + +func NewTimedOutTestStatus(message *string, exception *string, backtrace []string) TestStatus { + return TestStatus{ + Kind: TestStatusTimedOut, + Message: message, + Exception: exception, + Backtrace: backtrace, + } +} + +func NewTodoTestStatus(message *string) TestStatus { + return TestStatus{Kind: TestStatusTodo, Message: message} +} + +func NewQuarantinedTestStatus(originalStatus TestStatus) TestStatus { + return TestStatus{Kind: TestStatusQuarantined, OriginalStatus: &originalStatus} +} + +func (s TestStatus) ImpliesSkipped() bool { + return s.Kind == TestStatusPended || s.Kind == TestStatusSkipped || s.Kind == TestStatusTodo +} + +func (s TestStatus) ImpliesFailure() bool { + return s.Kind == TestStatusFailed || s.Kind == TestStatusCanceled || s.Kind == TestStatusTimedOut +} + +func (s TestStatus) PotentiallyFlaky() bool { + return s.Kind == TestStatusFailed || s.Kind == TestStatusTimedOut +} + +type TestAttempt struct { + Duration *time.Duration `json:"durationInNanoseconds"` + Meta map[string]any `json:"meta,omitempty"` + Status TestStatus `json:"status"` + Stderr *string `json:"stderr,omitempty"` + Stdout *string `json:"stdout,omitempty"` + StartedAt *time.Time `json:"startedAt,omitempty"` + FinishedAt *time.Time `json:"finishedAt,omitempty"` +} + +type Test struct { + ID *string `json:"id,omitempty"` + Name string `json:"name"` + Scope *string `json:"scope,omitempty"` + Lineage []string `json:"lineage,omitempty"` + Location *Location `json:"location,omitempty"` + Attempt TestAttempt `json:"attempt"` + PastAttempts []TestAttempt `json:"pastAttempts,omitempty"` +} + +func (t Test) Quarantine() Test { + if t.Attempt.Status.Kind == TestStatusQuarantined { + return t + } + + t.Attempt.Status = NewQuarantinedTestStatus(t.Attempt.Status) + return t +} + +func (t Test) Flaky() bool { + if len(t.PastAttempts) == 0 { + return false + } + + sawSuccess := false + sawPotentiallyFlaky := false + + if t.Attempt.Status.Kind == TestStatusSuccessful { + sawSuccess = true + } + if t.Attempt.Status.PotentiallyFlaky() { + sawPotentiallyFlaky = true + } + + for _, attempt := range t.PastAttempts { + if attempt.Status.Kind == TestStatusSuccessful { + sawSuccess = true + } + + if attempt.Status.PotentiallyFlaky() { + sawPotentiallyFlaky = true + } + } + + return sawSuccess && sawPotentiallyFlaky +} + +func (t Test) Tag(key string, value any) Test { + if t.Attempt.Meta == nil { + t.Attempt.Meta = map[string]any{} + } + + if t.Attempt.Meta["__rwx"] == nil { + t.Attempt.Meta["__rwx"] = map[string]any{key: value} + } else { + if rwxMeta, ok := t.Attempt.Meta["__rwx"].(map[string]any); ok { + rwxMeta[key] = value + t.Attempt.Meta["__rwx"] = rwxMeta + } + } + + return t +} + +func (t Test) Matches(other Test) bool { + return t.IdentityForMatching() == other.IdentityForMatching() +} + +func (t Test) IdentityForMatching() string { + scopeStr := "" + if t.Scope != nil { + scopeStr = *t.Scope + } + + idStr := "nil" + if t.ID != nil { + idStr = *t.ID + } + + locationFileStr := "nil" + if t.Location != nil { + locationFileStr = t.Location.File + } + + locationColumnStr := "nil" + if t.Location != nil && t.Location.Column != nil { + locationColumnStr = strconv.Itoa(*t.Location.Column) + } + + locationLineStr := "nil" + if t.Location != nil && t.Location.Line != nil { + locationLineStr = strconv.Itoa(*t.Location.Line) + } + + lineageStr := "" + for _, component := range t.Lineage { + lineageStr = lineageStr + "____" + component + } + + //nolint:lll + return fmt.Sprintf("scope=%s :: id=%s :: name=%s :: locationFile=%s :: locationColumn=%s :: locationLine=%s :: lineage=%s", scopeStr, idStr, t.Name, locationFileStr, locationColumnStr, locationLineStr, lineageStr) +} + +// Calculates the composite identifier of a Test given the components which determine it +func (t Test) Identify(recipe TestIdentityRecipe) (string, error) { + foundComponents := make([]string, 0) + + for _, component := range recipe.Components { + var getter func() (*string, error) + switch component { + case "description": + getter = t.nameGetter + case "file": + getter = t.fileGetter + case "id": + getter = t.idGetter + default: + getter = t.metaGetter(component) + } + + component, err := t.componentValue(recipe.Strict, getter) + if err != nil { + return "", err + } + foundComponents = append(foundComponents, *component) + } + + return strings.Join(foundComponents, " -captain- "), nil +} + +func (t Test) componentValue(strictly bool, getter func() (*string, error)) (*string, error) { + value, err := getter() + + switch { + case strictly && err != nil: + return nil, err + case err == nil && value == nil: + zero := "" + return &zero, nil + case err == nil && value != nil: + return value, nil + default: + missing := "MISSING_IDENTITY_COMPONENT" + return &missing, nil + } +} + +func (t Test) nameGetter() (*string, error) { + return &t.Name, nil +} + +func (t Test) fileGetter() (*string, error) { + if t.Location == nil { + return nil, errors.NewInternalError( + "Location is not defined for %v, but we tried to use it for identification.", + t, + ) + } + + return &t.Location.File, nil +} + +func (t Test) idGetter() (*string, error) { + if t.ID == nil { + return nil, errors.NewInternalError( + "ID is not defined for %v, but we tried to use it for identification.", + t, + ) + } + + return t.ID, nil +} + +func (t Test) metaGetter(component string) func() (*string, error) { + return func() (*string, error) { + if t.Attempt.Meta == nil { + return nil, errors.NewInternalError( + "Meta is not defined for %v, but we tried to get %s from it.", + t, + component, + ) + } + + value, exists := t.Attempt.Meta[component] + if !exists { + return nil, errors.NewInternalError( + "Tried to get %s from %v of %v, but it was not there.", + component, + t.Attempt.Meta, + t, + ) + } + + if value == nil { + return nil, nil + } + + formatted := fmt.Sprintf("%v", value) + return &formatted, nil + } +} diff --git a/internal/captain/testingschema/v1/test_results.go b/internal/captain/testingschema/v1/test_results.go new file mode 100644 index 0000000..647c2fa --- /dev/null +++ b/internal/captain/testingschema/v1/test_results.go @@ -0,0 +1,68 @@ +// testingschema/v1 holds the implementation of V1 of RWX's test results schema: +// https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json +package v1 + +import ( + "encoding/json" + + "github.com/rwx-cloud/cli/internal/captain/errors" +) + +type TestResults struct { + Framework Framework `json:"framework"` + Summary Summary `json:"summary"` + Tests []Test `json:"tests"` + OtherErrors []OtherError `json:"otherErrors,omitempty"` + DerivedFrom []OriginalTestResults `json:"derivedFrom,omitempty"` + Meta map[string]any `json:"meta,omitempty"` +} + +func (tr TestResults) MarshalJSON() ([]byte, error) { + type Alias TestResults + + json, err := json.Marshal(&struct { + Schema string `json:"$schema"` + Alias + }{ + Schema: "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + Alias: (Alias)(tr), + }) + + return json, errors.WithStack(err) +} + +func NewTestResults(framework Framework, tests []Test, otherErrors []OtherError) *TestResults { + return &TestResults{ + Framework: framework, + Summary: NewSummary(tests, otherErrors), + Tests: tests, + OtherErrors: otherErrors, + Meta: map[string]any{}, + } +} + +func (tr *TestResults) UnmarshalJSON(b []byte) error { + type Alias TestResults + var a struct { + Schema string `json:"$schema"` + Alias + } + + if err := json.Unmarshal(b, &a); err != nil { + return errors.WithStack(err) + } + if a.Schema != "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json" { + return errors.NewInputError("The parsed JSON is not the v1 RWX test results schema") + } + + *tr = TestResults{ + Framework: a.Framework, + Summary: a.Summary, + Tests: a.Tests, + OtherErrors: a.OtherErrors, + DerivedFrom: a.DerivedFrom, + Meta: a.Meta, + } + + return nil +} diff --git a/internal/captain/testingschema/v1/test_results_test.go b/internal/captain/testingschema/v1/test_results_test.go new file mode 100644 index 0000000..1cff063 --- /dev/null +++ b/internal/captain/testingschema/v1/test_results_test.go @@ -0,0 +1,110 @@ +package v1_test + +import ( + "encoding/json" + + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("TestResults", func() { + Describe("Marshal/UnmarshalJSON", func() { + It("round trips", func() { + testResults := v1.TestResults{ + Framework: v1.Framework{ + Language: "Ruby", + Kind: "RSpec", + }, + Summary: v1.Summary{ + Status: v1.SummaryStatusSuccessful, + Tests: 1, + OtherErrors: 2, + Retries: 3, + Canceled: 4, + Failed: 5, + Pended: 6, + Quarantined: 7, + Skipped: 8, + Successful: 9, + TimedOut: 10, + Todo: 11, + }, + Tests: []v1.Test{ + { + Name: "name of the test", + Attempt: v1.TestAttempt{ + Duration: nil, + Status: v1.TestStatus{Kind: "successful"}, + }, + }, + }, + } + + data, err := json.Marshal(testResults) + + Expect(err).To(BeNil()) + + var parsedTestResults v1.TestResults + err = json.Unmarshal([]byte(data), &parsedTestResults) + + Expect(err).To(BeNil()) + Expect(parsedTestResults).To(Equal(testResults)) + }) + }) + + Describe("MarshalJSON", func() { + It("includes the schema in the marshalled JSON", func() { + json, err := json.Marshal(v1.TestResults{Summary: v1.Summary{Status: v1.SummaryStatusSuccessful}}) + + Expect(err).To(BeNil()) + Expect(string(json)).To( + ContainSubstring( + `"$schema":"https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json"`, + ), + ) + }) + + It("marshals to a valid schema", func() { + json, err := json.Marshal( + v1.TestResults{ + Framework: v1.Framework{ + Language: "Ruby", + Kind: "RSpec", + }, + Summary: v1.Summary{ + Status: v1.SummaryStatusSuccessful, + Tests: 1, + OtherErrors: 2, + Retries: 3, + Canceled: 4, + Failed: 5, + Pended: 6, + Quarantined: 7, + Skipped: 8, + Successful: 9, + TimedOut: 10, + Todo: 11, + }, + Tests: []v1.Test{ + { + Name: "name of the test", + Attempt: v1.TestAttempt{ + Duration: nil, + Status: v1.TestStatus{Kind: "successful"}, + }, + }, + }, + }, + ) + + Expect(err).To(BeNil()) + Expect(string(json)).To(ContainSubstring(`"framework":{"language":"Ruby","kind":"RSpec"}`)) + Expect(string(json)).To(ContainSubstring(`"summary":{"status":{"kind":"successful"}`)) + Expect(string(json)).To( + ContainSubstring(`"tests":[{"name":"name of the test","attempt":{"durationInNanoseconds":null`), + ) + }) + }) +}) diff --git a/internal/captain/testingschema/v1/test_test.go b/internal/captain/testingschema/v1/test_test.go new file mode 100644 index 0000000..77dd8bf --- /dev/null +++ b/internal/captain/testingschema/v1/test_test.go @@ -0,0 +1,593 @@ +package v1_test + +import ( + "encoding/json" + "time" + + v1 "github.com/rwx-cloud/cli/internal/captain/testingschema/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Test", func() { + Describe("TestAttempt", func() { + It("serializes the duration to nanoseconds", func() { + duration := time.Duration(1500000000) + json, err := json.Marshal(v1.TestAttempt{Duration: &duration}) + + Expect(err).To(BeNil()) + Expect(string(json)).To(ContainSubstring(`"durationInNanoseconds":1500000000`)) + }) + + It("serializes no duration to null", func() { + json, err := json.Marshal(v1.TestAttempt{Duration: nil}) + + Expect(err).To(BeNil()) + Expect(string(json)).To(ContainSubstring(`"durationInNanoseconds":null`)) + }) + }) + + Describe("NewCanceledTestStatus", func() { + It("produces a Canceled test status", func() { + Expect(v1.NewCanceledTestStatus()).To(Equal( + v1.TestStatus{ + Kind: v1.TestStatusCanceled, + }, + )) + }) + }) + + Describe("NewFailedTestStatus", func() { + It("produces a Failed test status", func() { + message := "message" + exception := "exception" + backtrace := []string{"1", "2", "3"} + Expect(v1.NewFailedTestStatus(&message, &exception, backtrace)).To(Equal( + v1.TestStatus{ + Kind: v1.TestStatusFailed, + Message: &message, + Exception: &exception, + Backtrace: backtrace, + }, + )) + }) + }) + + Describe("NewPendedTestStatus", func() { + It("produces a Pended test status", func() { + message := "message" + Expect(v1.NewPendedTestStatus(&message)).To(Equal( + v1.TestStatus{ + Kind: v1.TestStatusPended, + Message: &message, + }, + )) + }) + }) + + Describe("NewSkippedTestStatus", func() { + It("produces a Skipped test status", func() { + message := "message" + Expect(v1.NewSkippedTestStatus(&message)).To(Equal( + v1.TestStatus{ + Kind: v1.TestStatusSkipped, + Message: &message, + }, + )) + }) + }) + + Describe("NewSuccessfulTestStatus", func() { + It("produces a Successful test status", func() { + Expect(v1.NewSuccessfulTestStatus()).To(Equal( + v1.TestStatus{ + Kind: v1.TestStatusSuccessful, + }, + )) + }) + }) + + Describe("NewTimedOutTestStatus", func() { + It("produces a TimedOut test status", func() { + Expect(v1.NewTimedOutTestStatus(nil, nil, nil)).To(Equal( + v1.TestStatus{ + Kind: v1.TestStatusTimedOut, + }, + )) + }) + }) + + Describe("NewTodoTestStatus", func() { + It("produces a Todo test status", func() { + message := "message" + Expect(v1.NewTodoTestStatus(&message)).To(Equal( + v1.TestStatus{ + Kind: v1.TestStatusTodo, + Message: &message, + }, + )) + }) + }) + + Describe("NewQuarantinedTestStatus", func() { + It("produces a Quarantined test status", func() { + originalStatus := v1.NewCanceledTestStatus() + Expect(v1.NewQuarantinedTestStatus(originalStatus)).To(Equal( + v1.TestStatus{ + Kind: v1.TestStatusQuarantined, + OriginalStatus: &originalStatus, + }, + )) + }) + }) + + Describe("ImpliesSkipped", func() { + It("implies skipped for failed statuses", func() { + Expect(v1.NewPendedTestStatus(nil).ImpliesSkipped()).To(Equal(true)) + }) + + It("implies skipped for canceled statuses", func() { + Expect(v1.NewTodoTestStatus(nil).ImpliesSkipped()).To(Equal(true)) + }) + + It("implies skipped for timed out statuses", func() { + Expect(v1.NewSkippedTestStatus(nil).ImpliesSkipped()).To(Equal(true)) + }) + + It("does not imply skipped for other statuses", func() { + Expect(v1.NewSuccessfulTestStatus().ImpliesSkipped()).To(Equal(false)) + }) + }) + + Describe("ImpliesFailure", func() { + It("implies failure for failed statuses", func() { + Expect(v1.NewFailedTestStatus(nil, nil, nil).ImpliesFailure()).To(Equal(true)) + }) + + It("implies failure for canceled statuses", func() { + Expect(v1.NewCanceledTestStatus().ImpliesFailure()).To(Equal(true)) + }) + + It("implies failure for timed out statuses", func() { + Expect(v1.NewTimedOutTestStatus(nil, nil, nil).ImpliesFailure()).To(Equal(true)) + }) + + It("does not imply failure for other statuses", func() { + Expect(v1.NewSuccessfulTestStatus().ImpliesFailure()).To(Equal(false)) + }) + }) + + Describe("PotentiallyFlaky", func() { + It("is potentially flaky for failed statuses", func() { + Expect(v1.NewFailedTestStatus(nil, nil, nil).PotentiallyFlaky()).To(Equal(true)) + }) + + It("is not potentially flaky for canceled statuses", func() { + Expect(v1.NewCanceledTestStatus().PotentiallyFlaky()).To(Equal(false)) + }) + + It("is potentially flaky for timed out statuses", func() { + Expect(v1.NewTimedOutTestStatus(nil, nil, nil).PotentiallyFlaky()).To(Equal(true)) + }) + + It("is not potentially flaky for other statuses", func() { + Expect(v1.NewSuccessfulTestStatus().PotentiallyFlaky()).To(Equal(false)) + }) + }) + + Describe("Flaky", func() { + It("is false for a test that passed", func() { + test := v1.Test{Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}} + Expect(test.Flaky()).To(Equal(false)) + }) + + It("is false for a test that failed", func() { + test := v1.Test{Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}} + Expect(test.Flaky()).To(Equal(false)) + }) + + It("is false for a test that timed out", func() { + test := v1.Test{Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}} + Expect(test.Flaky()).To(Equal(false)) + }) + + It("is false for a test that was canceled", func() { + test := v1.Test{Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}} + Expect(test.Flaky()).To(Equal(false)) + }) + + It("is true for a test that failed then passed", func() { + test := v1.Test{ + Attempt: v1.TestAttempt{Status: v1.NewSuccessfulTestStatus()}, + PastAttempts: []v1.TestAttempt{{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + } + Expect(test.Flaky()).To(Equal(true)) + }) + + It("is true for a test that passed then failed", func() { + test := v1.Test{ + Attempt: v1.TestAttempt{Status: v1.NewFailedTestStatus(nil, nil, nil)}, + PastAttempts: []v1.TestAttempt{{Status: v1.NewSuccessfulTestStatus()}}, + } + Expect(test.Flaky()).To(Equal(true)) + }) + + It("is true for a test that timed out and passed", func() { + test := v1.Test{ + Attempt: v1.TestAttempt{Status: v1.NewTimedOutTestStatus(nil, nil, nil)}, + PastAttempts: []v1.TestAttempt{{Status: v1.NewSuccessfulTestStatus()}}, + } + Expect(test.Flaky()).To(Equal(true)) + }) + + It("is false for a test that was canceled and passed", func() { + test := v1.Test{ + Attempt: v1.TestAttempt{Status: v1.NewCanceledTestStatus()}, + PastAttempts: []v1.TestAttempt{{Status: v1.NewSuccessfulTestStatus()}}, + } + Expect(test.Flaky()).To(Equal(false)) + }) + + It("is false for a test that was pended and failed", func() { + test := v1.Test{ + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + PastAttempts: []v1.TestAttempt{{Status: v1.NewFailedTestStatus(nil, nil, nil)}}, + } + Expect(test.Flaky()).To(Equal(false)) + }) + + It("is false for a test that was pended and passed", func() { + test := v1.Test{ + Attempt: v1.TestAttempt{Status: v1.NewPendedTestStatus(nil)}, + PastAttempts: []v1.TestAttempt{{Status: v1.NewSuccessfulTestStatus()}}, + } + Expect(test.Flaky()).To(Equal(false)) + }) + }) + + Describe("Quarantine", func() { + It("quarantines a test that is not quarantined", func() { + originalStatus := v1.NewFailedTestStatus(nil, nil, nil) + test := v1.Test{Attempt: v1.TestAttempt{Status: originalStatus}} + Expect(test.Attempt.Status).To(Equal(originalStatus)) + + quarantinedTest := test.Quarantine() + Expect(test.Attempt.Status).To(Equal(originalStatus)) + Expect(quarantinedTest.Attempt.Status).To(Equal(v1.NewQuarantinedTestStatus(originalStatus))) + }) + + It("does not double-quarantine a test", func() { + originalStatus := v1.NewFailedTestStatus(nil, nil, nil) + test := v1.Test{Attempt: v1.TestAttempt{Status: v1.NewQuarantinedTestStatus(originalStatus)}} + Expect(test.Attempt.Status).To(Equal(v1.NewQuarantinedTestStatus(originalStatus))) + + quarantinedTest := test.Quarantine() + Expect(test.Attempt.Status).To(Equal(v1.NewQuarantinedTestStatus(originalStatus))) + Expect(quarantinedTest).To(Equal(test)) + }) + }) + + Describe("Tag", func() { + It("adds RWX metadata to the test when there is no existing meta", func() { + Expect(v1.Test{Attempt: v1.TestAttempt{}}.Tag("some-key", true)).To(Equal( + v1.Test{ + Attempt: v1.TestAttempt{ + Meta: map[string]any{ + "__rwx": map[string]any{"some-key": true}, + }, + }, + }, + )) + }) + + It("adds RWX metadata to the test when there is existing meta, but not RWX meta", func() { + Expect( + v1.Test{ + Attempt: v1.TestAttempt{Meta: map[string]any{"foo": "bar"}}, + }.Tag("some-key", true), + ).To(Equal( + v1.Test{ + Attempt: v1.TestAttempt{ + Meta: map[string]any{ + "foo": "bar", + "__rwx": map[string]any{"some-key": true}, + }, + }, + }, + )) + }) + + It("adds RWX metadata to the test when there is existing RWX meta", func() { + Expect( + v1.Test{ + Attempt: v1.TestAttempt{Meta: map[string]any{ + "foo": "bar", + "__rwx": map[string]any{"something": "else"}, + }}, + }.Tag("some-key", true), + ).To(Equal( + v1.Test{ + Attempt: v1.TestAttempt{ + Meta: map[string]any{ + "foo": "bar", + "__rwx": map[string]any{ + "something": "else", + "some-key": true, + }, + }, + }, + }, + )) + }) + + It("does not add RWX metadata to the test when there is existing RWX meta in an unexpected shape", func() { + Expect( + v1.Test{ + Attempt: v1.TestAttempt{Meta: map[string]any{ + "foo": "bar", + "__rwx": true, + }}, + }.Tag("some-key", true), + ).To(Equal( + v1.Test{ + Attempt: v1.TestAttempt{ + Meta: map[string]any{ + "foo": "bar", + "__rwx": true, + }, + }, + }, + )) + }) + }) + + Describe("Matches", func() { + It("matches when the top-level fields are the same", func() { + scope1_1 := "scope1" + id1_1 := "id1" + name1_1 := "name1" + lineage1_1 := []string{"name", "1"} + file1_1 := "file1" + location1_1 := v1.Location{File: file1_1} + + scope1_2 := "scope1" + id1_2 := "id1" + name1_2 := "name1" + lineage1_2 := []string{"name", "1"} + file1_2 := "file1" + location1_2 := v1.Location{File: file1_2} + + test := v1.Test{ + Scope: &scope1_1, + ID: &id1_1, + Name: name1_1, + Lineage: lineage1_1, + Location: &location1_1, + } + + Expect(test.Matches(v1.Test{ + Scope: &scope1_2, + ID: &id1_2, + Name: name1_2, + Lineage: lineage1_2, + Location: &location1_2, + })).To(BeTrue()) + + Expect(test.Matches(v1.Test{ + Scope: nil, + ID: &id1_2, + Name: name1_2, + Lineage: lineage1_2, + Location: &location1_2, + })).To(BeFalse()) + + Expect(test.Matches(v1.Test{ + Scope: &scope1_2, + ID: nil, + Name: name1_2, + Lineage: lineage1_2, + Location: &location1_2, + })).To(BeFalse()) + + Expect(test.Matches(v1.Test{ + Scope: &scope1_2, + ID: &id1_2, + Name: name1_2, + Lineage: lineage1_2, + Location: nil, + })).To(BeFalse()) + + Expect(test.Matches(v1.Test{ + Scope: &scope1_2, + ID: &id1_2, + Name: "other name", + Lineage: lineage1_2, + Location: &location1_2, + })).To(BeFalse()) + + Expect(test.Matches(v1.Test{ + Scope: &scope1_2, + ID: &id1_2, + Name: name1_2, + Lineage: []string{"other"}, + Location: &location1_2, + })).To(BeFalse()) + }) + }) + + Describe("IdentityForMatching", func() { + It("constructs a string based on all of the attributes used for matching", func() { + scope1_1 := "scope1" + id1_1 := "id1" + name1_1 := "name1" + lineage1_1 := []string{"name", "1"} + file1_1 := "file1" + column := 1 + line := 2 + location1_1 := v1.Location{File: file1_1, Column: &column, Line: &line} + + test := v1.Test{ + Scope: &scope1_1, + ID: &id1_1, + Name: name1_1, + Lineage: lineage1_1, + Location: &location1_1, + } + + //nolint:lll + Expect(test.IdentityForMatching()).To(Equal("scope=scope1 :: id=id1 :: name=name1 :: locationFile=file1 :: locationColumn=1 :: locationLine=2 :: lineage=____name____1")) + }) + + It("uses 'nil' for some nil values and empty string for others", func() { + name1_1 := "name1" + lineage1_1 := []string{} + + test := v1.Test{ + Name: name1_1, + Lineage: lineage1_1, + } + + //nolint:lll + Expect(test.IdentityForMatching()).To(Equal("scope= :: id=nil :: name=name1 :: locationFile=nil :: locationColumn=nil :: locationLine=nil :: lineage=")) + }) + }) + + Describe("Identify", func() { + Context("with strict identification", func() { + It("returns an error when fetching from meta of a test without meta", func() { + compositeIdentifier, err := v1.Test{ + Name: "the-description", + Attempt: v1.TestAttempt{}, + }.Identify(v1.TestIdentityRecipe{Components: []string{"description", "something_from_meta"}, Strict: true}) + + Expect(compositeIdentifier).To(Equal("")) + Expect(err.Error()).To(ContainSubstring("Meta is not defined")) + }) + + It("returns an error when fetching from meta of a test without the component in meta", func() { + compositeIdentifier, err := v1.Test{ + Name: "the-description", + Attempt: v1.TestAttempt{ + Meta: map[string]any{"something_else_in_meta": 1}, + }, + }.Identify(v1.TestIdentityRecipe{Components: []string{"description", "something_from_meta"}, Strict: true}) + + Expect(compositeIdentifier).To(Equal("")) + Expect(err.Error()).To(ContainSubstring("it was not there")) + }) + + It("returns an error when the fetching the file without a location", func() { + compositeIdentifier, err := v1.Test{ + Name: "the-description", + }.Identify(v1.TestIdentityRecipe{Components: []string{"description", "file"}, Strict: true}) + + Expect(compositeIdentifier).To(Equal("")) + Expect(err.Error()).To(ContainSubstring("Location is not defined")) + }) + + It("returns an error when the fetching the ID when it's not there", func() { + compositeIdentifier, err := v1.Test{ + Name: "the-description", + }.Identify(v1.TestIdentityRecipe{Components: []string{"description", "id"}, Strict: true}) + + Expect(compositeIdentifier).To(Equal("")) + Expect(err.Error()).To(ContainSubstring("ID is not defined")) + }) + + It("returns a composite identifier otherwise", func() { + id := "the-id" + compositeIdentifier, err := v1.Test{ + ID: &id, + Name: "the-description", + Location: &v1.Location{File: "the-file"}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{ + "foo": 1, + "bar": "hello", + "baz": false, + "nil": nil, + }, + }, + }.Identify(v1.TestIdentityRecipe{ + Components: []string{"id", "description", "file", "foo", "bar", "baz", "nil"}, + Strict: true, + }) + + Expect(compositeIdentifier).To(Equal( + "the-id -captain- the-description -captain- the-file -captain-" + + " 1 -captain- hello -captain- false -captain- ", + )) + Expect(err).To(BeNil()) + }) + }) + + Context("without strict identification", func() { + It("returns a composite identifier when fetching from meta of a test without meta", func() { + compositeIdentifier, err := v1.Test{ + Name: "the-description", + Attempt: v1.TestAttempt{}, + }.Identify(v1.TestIdentityRecipe{Components: []string{"description", "something_from_meta"}, Strict: false}) + + Expect(compositeIdentifier).To(Equal("the-description -captain- MISSING_IDENTITY_COMPONENT")) + Expect(err).To(BeNil()) + }) + + It("returns a composite identifier when fetching from meta of a test without the component in meta", func() { + compositeIdentifier, err := v1.Test{ + Name: "the-description", + Attempt: v1.TestAttempt{ + Meta: map[string]any{"something_else_in_meta": 1}, + }, + }.Identify(v1.TestIdentityRecipe{Components: []string{"description", "something_from_meta"}, Strict: false}) + + Expect(compositeIdentifier).To(Equal("the-description -captain- MISSING_IDENTITY_COMPONENT")) + Expect(err).To(BeNil()) + }) + + It("returns a composite identifier when fetching the file without a location", func() { + compositeIdentifier, err := v1.Test{ + Name: "the-description", + }.Identify(v1.TestIdentityRecipe{Components: []string{"description", "file"}, Strict: false}) + + Expect(compositeIdentifier).To(Equal("the-description -captain- MISSING_IDENTITY_COMPONENT")) + Expect(err).To(BeNil()) + }) + + It("returns a composite identifier when fetching the ID when it's not there", func() { + compositeIdentifier, err := v1.Test{ + Name: "the-description", + }.Identify(v1.TestIdentityRecipe{Components: []string{"description", "id"}, Strict: false}) + + Expect(compositeIdentifier).To(Equal("the-description -captain- MISSING_IDENTITY_COMPONENT")) + Expect(err).To(BeNil()) + }) + + It("returns a composite identifier otherwise", func() { + id := "the-id" + compositeIdentifier, err := v1.Test{ + ID: &id, + Name: "the-description", + Location: &v1.Location{File: "the-file"}, + Attempt: v1.TestAttempt{ + Meta: map[string]any{ + "foo": 1, + "bar": "hello", + "baz": false, + "nil": nil, + }, + }, + }.Identify(v1.TestIdentityRecipe{ + Components: []string{"id", "description", "file", "foo", "bar", "baz", "nil"}, + Strict: false, + }) + + Expect(compositeIdentifier).To(Equal( + "the-id -captain- the-description -captain- the-file -captain-" + + " 1 -captain- hello -captain- false -captain- ", + )) + Expect(err).To(BeNil()) + }) + }) + }) +}) diff --git a/internal/captain/testingschema/v1/v1_suite_test.go b/internal/captain/testingschema/v1/v1_suite_test.go new file mode 100644 index 0000000..f6f64ef --- /dev/null +++ b/internal/captain/testingschema/v1/v1_suite_test.go @@ -0,0 +1,15 @@ +package v1_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestTesting(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "Testing Schema V1 Suite") +} diff --git a/internal/git/client.go b/internal/git/client.go index 73defc6..b54025c 100644 --- a/internal/git/client.go +++ b/internal/git/client.go @@ -38,6 +38,30 @@ func (c *Client) IsInsideWorkTree() bool { return strings.TrimSpace(string(out)) == "true" } +func (c *Client) GetUser() string { + cmd := exec.Command(c.Binary, "config", "user.name") + cmd.Dir = c.Dir + + out, err := cmd.Output() + if err != nil { + return "" + } + + return strings.TrimSpace(string(out)) +} + +func (c *Client) GetHeadSha() string { + cmd := exec.Command(c.Binary, "rev-parse", "HEAD") + cmd.Dir = c.Dir + + out, err := cmd.Output() + if err != nil { + return "" + } + + return strings.TrimSpace(string(out)) +} + func (c *Client) GetBranch() string { cmd := exec.Command(c.Binary, "branch", "--show-current") cmd.Dir = c.Dir diff --git a/internal/test/fixtures/cucumber-js.json b/internal/test/fixtures/cucumber-js.json new file mode 100644 index 0000000..5b5becd --- /dev/null +++ b/internal/test/fixtures/cucumber-js.json @@ -0,0 +1,447 @@ +[ + { + "description": "", + "elements": [ + { + "description": "", + "id": "rule-sample;this-is-a-rule;a-passing-example", + "keyword": "Example", + "line": 7, + "name": "A passing example", + "steps": [ + { + "arguments": [], + "keyword": "Given ", + "line": 8, + "name": "this will pass", + "match": { + "location": "features/support/steps.js:4" + }, + "result": { + "status": "passed", + "duration": 142791 + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 9, + "name": "I do an action", + "match": { + "location": "features/support/steps.js:12" + }, + "result": { + "status": "passed", + "duration": 16584 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 10, + "name": "some results should be there", + "match": { + "location": "features/support/steps.js:14" + }, + "result": { + "status": "passed", + "duration": 20582 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag" + } + ], + "type": "scenario" + }, + { + "description": "", + "id": "rule-sample;this-is-a-rule;a-failing-example", + "keyword": "Example", + "line": 14, + "name": "A failing example", + "steps": [ + { + "arguments": [], + "keyword": "Given ", + "line": 15, + "name": "this will fail", + "match": { + "location": "features/support/steps.js:8" + }, + "result": { + "status": "passed", + "duration": 28541 + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 16, + "name": "I do an action", + "match": { + "location": "features/support/steps.js:12" + }, + "result": { + "status": "passed", + "duration": 12542 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 17, + "name": "some results should be there", + "match": { + "location": "features/support/steps.js:14" + }, + "result": { + "status": "failed", + "duration": 67957, + "error_message": "AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:\n\n assert(this.this_will_pass === true)\n\n + expected - actual\n\n -false\n +true\n\n at World. (/Users/kylekthompson/src/captain-examples/cucumber-js/features/support/steps.js:15:3)" + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag" + }, + { + "name": "@flaky", + "line": 12 + }, + { + "name": "@failing", + "line": 13 + } + ], + "type": "scenario" + }, + { + "description": "", + "id": "rule-sample;this-is-a-rule;a-pending-example", + "keyword": "Example", + "line": 19, + "name": "A pending example", + "steps": [ + { + "arguments": [], + "keyword": "Given ", + "line": 20, + "name": "this is pending", + "match": { + "location": "features/support/steps.js:18" + }, + "result": { + "status": "pending", + "duration": 22791 + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 21, + "name": "I do an action", + "match": { + "location": "features/support/steps.js:12" + }, + "result": { + "status": "skipped", + "duration": 0 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 22, + "name": "nothing should happen", + "result": { + "status": "undefined", + "duration": 0 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag" + } + ], + "type": "scenario" + }, + { + "description": "", + "id": "rule-sample;this-is-a-rule;a-skipped-example", + "keyword": "Example", + "line": 24, + "name": "A skipped example", + "steps": [ + { + "arguments": [], + "keyword": "Given ", + "line": 25, + "name": "this is skipped", + "match": { + "location": "features/support/steps.js:22" + }, + "result": { + "status": "skipped", + "duration": 91374 + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 26, + "name": "I do an action", + "match": { + "location": "features/support/steps.js:12" + }, + "result": { + "status": "skipped", + "duration": 0 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 27, + "name": "nothing should happen", + "result": { + "status": "undefined", + "duration": 0 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag" + } + ], + "type": "scenario" + }, + { + "description": "", + "id": "rule-sample;this-is-a-rule;an-undefined-example", + "keyword": "Example", + "line": 29, + "name": "An undefined example", + "steps": [ + { + "arguments": [], + "keyword": "Given ", + "line": 30, + "name": "this is undefined", + "result": { + "status": "undefined", + "duration": 0 + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 31, + "name": "I do an action", + "match": { + "location": "features/support/steps.js:12" + }, + "result": { + "status": "skipped", + "duration": 0 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 32, + "name": "nothing should happen", + "result": { + "status": "undefined", + "duration": 0 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag" + } + ], + "type": "scenario" + }, + { + "description": "", + "id": "rule-sample;this-is-a-rule;with-a-failing-before-hook", + "keyword": "Example", + "line": 35, + "name": "With a failing before hook", + "steps": [ + { + "keyword": "Before", + "hidden": true, + "result": { + "status": "failed", + "duration": 104333, + "error_message": "Error: failed in before hook\n at World. (/Users/kylekthompson/src/captain-examples/cucumber-js/features/support/steps.js:27:9)" + } + }, + { + "arguments": [], + "keyword": "Given ", + "line": 36, + "name": "this will pass", + "match": { + "location": "features/support/steps.js:4" + }, + "result": { + "status": "skipped", + "duration": 0 + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 37, + "name": "I do an action", + "match": { + "location": "features/support/steps.js:12" + }, + "result": { + "status": "skipped", + "duration": 0 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 38, + "name": "some results should be there", + "match": { + "location": "features/support/steps.js:14" + }, + "result": { + "status": "skipped", + "duration": 0 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag" + }, + { + "name": "@failing_before_hook", + "line": 34 + } + ], + "type": "scenario" + }, + { + "description": "", + "id": "rule-sample;this-is-a-rule;with-a-failing-after-hook", + "keyword": "Example", + "line": 41, + "name": "With a failing after hook", + "steps": [ + { + "arguments": [], + "keyword": "Given ", + "line": 42, + "name": "this will pass", + "match": { + "location": "features/support/steps.js:4" + }, + "result": { + "status": "passed", + "duration": 87083 + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 43, + "name": "I do an action", + "match": { + "location": "features/support/steps.js:12" + }, + "result": { + "status": "passed", + "duration": 9459 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 44, + "name": "some results should be there", + "match": { + "location": "features/support/steps.js:14" + }, + "result": { + "status": "passed", + "duration": 6999 + } + }, + { + "keyword": "After", + "hidden": true, + "result": { + "status": "failed", + "duration": 14750, + "error_message": "Error: failed in after hook\n at World. (/Users/kylekthompson/src/captain-examples/cucumber-js/features/support/steps.js:31:9)" + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag" + }, + { + "name": "@failing_after_hook", + "line": 40 + } + ], + "type": "scenario" + } + ], + "id": "rule-sample", + "line": 2, + "keyword": "Feature", + "name": "Rule Sample", + "tags": [ + { + "name": "@feature_tag", + "line": 1 + } + ], + "uri": "features/rule.feature" + } +] diff --git a/internal/test/fixtures/cucumber/after_fail.json b/internal/test/fixtures/cucumber/after_fail.json new file mode 100644 index 0000000..a8e4b99 --- /dev/null +++ b/internal/test/fixtures/cucumber/after_fail.json @@ -0,0 +1,90 @@ +[ + { + "id": "rule-sample", + "uri": "features/rule.feature", + "keyword": "Feature", + "name": "Rule Sample", + "description": "", + "line": 2, + "tags": [ + { + "name": "@feature_tag", + "line": 1 + } + ], + "elements": [ + { + "id": "rule-sample;with-a-failing-after-hook", + "keyword": "Example", + "name": "With a failing after hook", + "description": "", + "line": 41, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will pass", + "line": 42, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "passed", + "duration": 12000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 43, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "passed", + "duration": 7000 + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 44, + "match": { + "location": "features/step_definitions/steps.rb:12" + }, + "result": { + "status": "passed", + "duration": 30000 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing_after_hook", + "line": 40 + } + ], + "after": [ + { + "match": { + "location": "features/step_definitions/steps.rb:28" + }, + "result": { + "status": "failed", + "error_message": "failed in after hook (RuntimeError)\n./features/step_definitions/steps.rb:29:in `After'", + "duration": 129000 + } + } + ] + } + ] + } +] diff --git a/internal/test/fixtures/cucumber/background_integration.json b/internal/test/fixtures/cucumber/background_integration.json new file mode 100644 index 0000000..3419e1a --- /dev/null +++ b/internal/test/fixtures/cucumber/background_integration.json @@ -0,0 +1,514 @@ +[ + { + "id": "background-sample", + "uri": "features/background.feature", + "keyword": "Feature", + "name": "Background Sample", + "description": "", + "line": 1, + "elements": [ + { + "keyword": "Background", + "name": "", + "description": "", + "line": 3, + "type": "background", + "steps": [ + { + "keyword": "Given ", + "name": "I share some background", + "line": 4, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "passed", + "duration": 18000 + } + } + ] + }, + { + "id": "background-sample;a-passing-example", + "keyword": "Scenario", + "name": "A passing example", + "description": "", + "line": 6, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will pass", + "line": 7, + "match": { + "location": "features/step_definitions/steps.rb:5" + }, + "result": { + "status": "passed", + "duration": 3000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 8, + "match": { + "location": "features/step_definitions/steps.rb:13" + }, + "result": { + "status": "passed", + "duration": 2000 + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 9, + "match": { + "location": "features/step_definitions/steps.rb:16" + }, + "result": { + "status": "passed", + "duration": 561000 + } + } + ] + }, + { + "keyword": "Background", + "name": "", + "description": "", + "line": 3, + "type": "background", + "steps": [ + { + "keyword": "Given ", + "name": "I share some background", + "line": 4, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "passed", + "duration": 3000 + } + } + ] + }, + { + "id": "background-sample;a-failing-example", + "keyword": "Example", + "name": "A failing example", + "description": "", + "line": 12, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will fail", + "line": 13, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "passed", + "duration": 2000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 14, + "match": { + "location": "features/step_definitions/steps.rb:13" + }, + "result": { + "status": "passed", + "duration": 2000 + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 15, + "match": { + "location": "features/step_definitions/steps.rb:16" + }, + "result": { + "status": "failed", + "error_message": "\nexpected true\n got false\n (RSpec::Expectations::ExpectationNotMetError)\n./features/step_definitions/steps.rb:17:in `\"some results should be there\"'\nfeatures/background.feature:15:in `some results should be there'", + "duration": 19080000 + } + } + ], + "tags": [ + { + "name": "@failing", + "line": 11 + } + ] + }, + { + "keyword": "Background", + "name": "", + "description": "", + "line": 3, + "type": "background", + "steps": [ + { + "keyword": "Given ", + "name": "I share some background", + "line": 4, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "passed", + "duration": 12000 + } + } + ] + }, + { + "id": "background-sample;a-pending-example", + "keyword": "Example", + "name": "A pending example", + "description": "", + "line": 17, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this is pending", + "line": 18, + "match": { + "location": "features/step_definitions/steps.rb:20" + }, + "result": { + "status": "pending", + "error_message": "TODO (Cucumber::Pending)\n./features/step_definitions/steps.rb:21:in `\"this is pending\"'\nfeatures/background.feature:18:in `this is pending'", + "duration": 48000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 19, + "match": { + "location": "features/step_definitions/steps.rb:13" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "nothing should happen", + "line": 20, + "match": { + "location": "features/background.feature:20" + }, + "result": { + "status": "undefined" + } + } + ] + }, + { + "keyword": "Background", + "name": "", + "description": "", + "line": 3, + "type": "background", + "steps": [ + { + "keyword": "Given ", + "name": "I share some background", + "line": 4, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "passed", + "duration": 5000 + } + } + ] + }, + { + "id": "background-sample;a-skipped-example", + "keyword": "Example", + "name": "A skipped example", + "description": "", + "line": 22, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this is skipped", + "line": 23, + "match": { + "location": "features/step_definitions/steps.rb:24" + }, + "result": { + "status": "skipped", + "duration": 50000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 24, + "match": { + "location": "features/step_definitions/steps.rb:13" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "nothing should happen", + "line": 25, + "match": { + "location": "features/background.feature:25" + }, + "result": { + "status": "undefined" + } + } + ] + }, + { + "keyword": "Background", + "name": "", + "description": "", + "line": 3, + "type": "background", + "steps": [ + { + "keyword": "Given ", + "name": "I share some background", + "line": 4, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "passed", + "duration": 4000 + } + } + ] + }, + { + "id": "background-sample;an-undefined-example", + "keyword": "Example", + "name": "An undefined example", + "description": "", + "line": 27, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this is undefined", + "line": 28, + "match": { + "location": "features/background.feature:28" + }, + "result": { + "status": "undefined" + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 29, + "match": { + "location": "features/step_definitions/steps.rb:13" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "nothing should happen", + "line": 30, + "match": { + "location": "features/background.feature:30" + }, + "result": { + "status": "undefined" + } + } + ] + }, + { + "keyword": "Background", + "name": "", + "description": "", + "line": 3, + "type": "background", + "steps": [ + { + "keyword": "Given ", + "name": "I share some background", + "line": 4, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "skipped" + } + } + ], + "before": [ + { + "match": { + "location": "features/step_definitions/steps.rb:28" + }, + "result": { + "status": "failed", + "error_message": "failed in before hook (RuntimeError)\n./features/step_definitions/steps.rb:29:in `Before'", + "duration": 64000 + } + } + ] + }, + { + "id": "background-sample;with-a-failing-before-hook", + "keyword": "Example", + "name": "With a failing before hook", + "description": "", + "line": 33, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will pass", + "line": 34, + "match": { + "location": "features/step_definitions/steps.rb:5" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 35, + "match": { + "location": "features/step_definitions/steps.rb:13" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 36, + "match": { + "location": "features/step_definitions/steps.rb:16" + }, + "result": { + "status": "skipped" + } + } + ], + "tags": [ + { + "name": "@failing_before_hook", + "line": 32 + } + ] + }, + { + "keyword": "Background", + "name": "", + "description": "", + "line": 3, + "type": "background", + "steps": [ + { + "keyword": "Given ", + "name": "I share some background", + "line": 4, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "passed", + "duration": 7000 + } + } + ] + }, + { + "id": "background-sample;with-a-failing-after-hook", + "keyword": "Example", + "name": "With a failing after hook", + "description": "", + "line": 39, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will pass", + "line": 40, + "match": { + "location": "features/step_definitions/steps.rb:5" + }, + "result": { + "status": "passed", + "duration": 3000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 41, + "match": { + "location": "features/step_definitions/steps.rb:13" + }, + "result": { + "status": "passed", + "duration": 2000 + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 42, + "match": { + "location": "features/step_definitions/steps.rb:16" + }, + "result": { + "status": "passed", + "duration": 15000 + } + } + ], + "tags": [ + { + "name": "@failing_after_hook", + "line": 38 + } + ], + "after": [ + { + "match": { + "location": "features/step_definitions/steps.rb:32" + }, + "result": { + "status": "failed", + "error_message": "failed in after hook (RuntimeError)\n./features/step_definitions/steps.rb:33:in `After'", + "duration": 38000 + } + } + ] + } + ] + } +] diff --git a/internal/test/fixtures/cucumber/before_fail.json b/internal/test/fixtures/cucumber/before_fail.json new file mode 100644 index 0000000..6ce3318 --- /dev/null +++ b/internal/test/fixtures/cucumber/before_fail.json @@ -0,0 +1,87 @@ +[ + { + "id": "rule-sample", + "uri": "features/rule.feature", + "keyword": "Feature", + "name": "Rule Sample", + "description": "", + "line": 2, + "tags": [ + { + "name": "@feature_tag", + "line": 1 + } + ], + "elements": [ + { + "id": "rule-sample;with-a-failing-before-hook", + "keyword": "Example", + "name": "With a failing before hook", + "description": "", + "line": 34, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will pass", + "line": 35, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 36, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 37, + "match": { + "location": "features/step_definitions/steps.rb:12" + }, + "result": { + "status": "skipped" + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing_before_hook", + "line": 33 + } + ], + "before": [ + { + "match": { + "location": "features/step_definitions/steps.rb:24" + }, + "result": { + "status": "failed", + "error_message": "failed in before hook (RuntimeError)\n./features/step_definitions/steps.rb:25:in `Before'", + "duration": 141000 + } + } + ] + } + ] + } +] diff --git a/internal/test/fixtures/cucumber/failing.json b/internal/test/fixtures/cucumber/failing.json new file mode 100644 index 0000000..7a238ba --- /dev/null +++ b/internal/test/fixtures/cucumber/failing.json @@ -0,0 +1,79 @@ +[ + { + "id": "rule-sample", + "uri": "features/rule.feature", + "keyword": "Feature", + "name": "Rule Sample", + "description": "", + "line": 2, + "tags": [ + { + "name": "@feature_tag", + "line": 1 + } + ], + "elements": [ + { + "id": "rule-sample;a-failing-example", + "keyword": "Example", + "name": "A failing example", + "description": "", + "line": 13, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will fail", + "line": 14, + "match": { + "location": "features/step_definitions/steps.rb:5" + }, + "result": { + "status": "passed", + "duration": 11000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 15, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "passed", + "duration": 7000 + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 16, + "match": { + "location": "features/step_definitions/steps.rb:12" + }, + "result": { + "status": "failed", + "error_message": "\nexpected true\n got false\n (RSpec::Expectations::ExpectationNotMetError)\n./features/step_definitions/steps.rb:13:in `\"some results should be there\"'\nfeatures/rule.feature:16:in `some results should be there'", + "duration": 26510000 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing", + "line": 12 + } + ] + } + ] + } +] diff --git a/internal/test/fixtures/cucumber/integration.json b/internal/test/fixtures/cucumber/integration.json new file mode 100644 index 0000000..57a10ec --- /dev/null +++ b/internal/test/fixtures/cucumber/integration.json @@ -0,0 +1,438 @@ +[ + { + "id": "rule-sample", + "uri": "features/rule.feature", + "keyword": "Feature", + "name": "Rule Sample", + "description": "", + "line": 2, + "tags": [ + { + "name": "@feature_tag", + "line": 1 + } + ], + "elements": [ + { + "id": "rule-sample;a-passing-example", + "keyword": "Example", + "name": "A passing example", + "description": "", + "line": 7, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will pass", + "line": 8, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "passed", + "duration": 48000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 9, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "passed", + "duration": 8000 + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 10, + "match": { + "location": "features/step_definitions/steps.rb:12" + }, + "result": { + "status": "passed", + "duration": 1133000 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + { + "id": "rule-sample;a-failing-example", + "keyword": "Example", + "name": "A failing example", + "description": "", + "line": 13, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will fail", + "line": 14, + "match": { + "location": "features/step_definitions/steps.rb:5" + }, + "result": { + "status": "passed", + "duration": 11000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 15, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "passed", + "duration": 7000 + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 16, + "match": { + "location": "features/step_definitions/steps.rb:12" + }, + "result": { + "status": "failed", + "error_message": "\nexpected true\n got false\n (RSpec::Expectations::ExpectationNotMetError)\n./features/step_definitions/steps.rb:13:in `\"some results should be there\"'\nfeatures/rule.feature:16:in `some results should be there'", + "duration": 26510000 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing", + "line": 12 + } + ] + }, + { + "id": "rule-sample;a-pending-example", + "keyword": "Example", + "name": "A pending example", + "description": "", + "line": 18, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this is pending", + "line": 19, + "match": { + "location": "features/step_definitions/steps.rb:16" + }, + "result": { + "status": "pending", + "error_message": "TODO (Cucumber::Pending)\n./features/step_definitions/steps.rb:17:in `\"this is pending\"'\nfeatures/rule.feature:19:in `this is pending'", + "duration": 158000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 20, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "nothing should happen", + "line": 21, + "match": { + "location": "features/rule.feature:21" + }, + "result": { + "status": "undefined" + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + { + "id": "rule-sample;a-skipped-example", + "keyword": "Example", + "name": "A skipped example", + "description": "", + "line": 23, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this is skipped", + "line": 24, + "match": { + "location": "features/step_definitions/steps.rb:20" + }, + "result": { + "status": "skipped", + "duration": 139000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 25, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "nothing should happen", + "line": 26, + "match": { + "location": "features/rule.feature:26" + }, + "result": { + "status": "undefined" + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + { + "id": "rule-sample;an-undefined-example", + "keyword": "Example", + "name": "An undefined example", + "description": "", + "line": 28, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this is undefined", + "line": 29, + "match": { + "location": "features/rule.feature:29" + }, + "result": { + "status": "undefined" + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 30, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "nothing should happen", + "line": 31, + "match": { + "location": "features/rule.feature:31" + }, + "result": { + "status": "undefined" + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + { + "id": "rule-sample;with-a-failing-before-hook", + "keyword": "Example", + "name": "With a failing before hook", + "description": "", + "line": 34, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will pass", + "line": 35, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 36, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 37, + "match": { + "location": "features/step_definitions/steps.rb:12" + }, + "result": { + "status": "skipped" + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing_before_hook", + "line": 33 + } + ], + "before": [ + { + "match": { + "location": "features/step_definitions/steps.rb:24" + }, + "result": { + "status": "failed", + "error_message": "failed in before hook (RuntimeError)\n./features/step_definitions/steps.rb:25:in `Before'", + "duration": 141000 + } + } + ] + }, + { + "id": "rule-sample;with-a-failing-after-hook", + "keyword": "Example", + "name": "With a failing after hook", + "description": "", + "line": 41, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will pass", + "line": 42, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "passed", + "duration": 12000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 43, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "passed", + "duration": 7000 + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 44, + "match": { + "location": "features/step_definitions/steps.rb:12" + }, + "result": { + "status": "passed", + "duration": 30000 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing_after_hook", + "line": 40 + } + ], + "after": [ + { + "match": { + "location": "features/step_definitions/steps.rb:28" + }, + "result": { + "status": "failed", + "error_message": "failed in after hook (RuntimeError)\n./features/step_definitions/steps.rb:29:in `After'", + "duration": 129000 + } + } + ] + } + ] + } +] diff --git a/internal/test/fixtures/cucumber/passing.json b/internal/test/fixtures/cucumber/passing.json new file mode 100644 index 0000000..ff5c5fc --- /dev/null +++ b/internal/test/fixtures/cucumber/passing.json @@ -0,0 +1,74 @@ +[ + { + "id": "rule-sample", + "uri": "features/rule.feature", + "keyword": "Feature", + "name": "Rule Sample", + "description": "", + "line": 2, + "tags": [ + { + "name": "@feature_tag", + "line": 1 + } + ], + "elements": [ + { + "id": "rule-sample;a-passing-example", + "keyword": "Example", + "name": "A passing example", + "description": "", + "line": 7, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will pass", + "line": 8, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "passed", + "duration": 48000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 9, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "passed", + "duration": 8000 + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 10, + "match": { + "location": "features/step_definitions/steps.rb:12" + }, + "result": { + "status": "passed", + "duration": 1133000 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + } + ] + } +] diff --git a/internal/test/fixtures/cucumber/pending.json b/internal/test/fixtures/cucumber/pending.json new file mode 100644 index 0000000..537f2f6 --- /dev/null +++ b/internal/test/fixtures/cucumber/pending.json @@ -0,0 +1,73 @@ +[ + { + "id": "rule-sample", + "uri": "features/rule.feature", + "keyword": "Feature", + "name": "Rule Sample", + "description": "", + "line": 2, + "tags": [ + { + "name": "@feature_tag", + "line": 1 + } + ], + "elements": [ + { + "id": "rule-sample;a-pending-example", + "keyword": "Example", + "name": "A pending example", + "description": "", + "line": 18, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this is pending", + "line": 19, + "match": { + "location": "features/step_definitions/steps.rb:16" + }, + "result": { + "status": "pending", + "error_message": "TODO (Cucumber::Pending)\n./features/step_definitions/steps.rb:17:in `\"this is pending\"'\nfeatures/rule.feature:19:in `this is pending'", + "duration": 158000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 20, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "nothing should happen", + "line": 21, + "match": { + "location": "features/rule.feature:21" + }, + "result": { + "status": "undefined" + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + } + ] + } +] diff --git a/internal/test/fixtures/cucumber/skipped.json b/internal/test/fixtures/cucumber/skipped.json new file mode 100644 index 0000000..57a10ec --- /dev/null +++ b/internal/test/fixtures/cucumber/skipped.json @@ -0,0 +1,438 @@ +[ + { + "id": "rule-sample", + "uri": "features/rule.feature", + "keyword": "Feature", + "name": "Rule Sample", + "description": "", + "line": 2, + "tags": [ + { + "name": "@feature_tag", + "line": 1 + } + ], + "elements": [ + { + "id": "rule-sample;a-passing-example", + "keyword": "Example", + "name": "A passing example", + "description": "", + "line": 7, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will pass", + "line": 8, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "passed", + "duration": 48000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 9, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "passed", + "duration": 8000 + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 10, + "match": { + "location": "features/step_definitions/steps.rb:12" + }, + "result": { + "status": "passed", + "duration": 1133000 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + { + "id": "rule-sample;a-failing-example", + "keyword": "Example", + "name": "A failing example", + "description": "", + "line": 13, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will fail", + "line": 14, + "match": { + "location": "features/step_definitions/steps.rb:5" + }, + "result": { + "status": "passed", + "duration": 11000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 15, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "passed", + "duration": 7000 + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 16, + "match": { + "location": "features/step_definitions/steps.rb:12" + }, + "result": { + "status": "failed", + "error_message": "\nexpected true\n got false\n (RSpec::Expectations::ExpectationNotMetError)\n./features/step_definitions/steps.rb:13:in `\"some results should be there\"'\nfeatures/rule.feature:16:in `some results should be there'", + "duration": 26510000 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing", + "line": 12 + } + ] + }, + { + "id": "rule-sample;a-pending-example", + "keyword": "Example", + "name": "A pending example", + "description": "", + "line": 18, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this is pending", + "line": 19, + "match": { + "location": "features/step_definitions/steps.rb:16" + }, + "result": { + "status": "pending", + "error_message": "TODO (Cucumber::Pending)\n./features/step_definitions/steps.rb:17:in `\"this is pending\"'\nfeatures/rule.feature:19:in `this is pending'", + "duration": 158000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 20, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "nothing should happen", + "line": 21, + "match": { + "location": "features/rule.feature:21" + }, + "result": { + "status": "undefined" + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + { + "id": "rule-sample;a-skipped-example", + "keyword": "Example", + "name": "A skipped example", + "description": "", + "line": 23, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this is skipped", + "line": 24, + "match": { + "location": "features/step_definitions/steps.rb:20" + }, + "result": { + "status": "skipped", + "duration": 139000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 25, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "nothing should happen", + "line": 26, + "match": { + "location": "features/rule.feature:26" + }, + "result": { + "status": "undefined" + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + { + "id": "rule-sample;an-undefined-example", + "keyword": "Example", + "name": "An undefined example", + "description": "", + "line": 28, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this is undefined", + "line": 29, + "match": { + "location": "features/rule.feature:29" + }, + "result": { + "status": "undefined" + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 30, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "nothing should happen", + "line": 31, + "match": { + "location": "features/rule.feature:31" + }, + "result": { + "status": "undefined" + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + }, + { + "id": "rule-sample;with-a-failing-before-hook", + "keyword": "Example", + "name": "With a failing before hook", + "description": "", + "line": 34, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will pass", + "line": 35, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 36, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 37, + "match": { + "location": "features/step_definitions/steps.rb:12" + }, + "result": { + "status": "skipped" + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing_before_hook", + "line": 33 + } + ], + "before": [ + { + "match": { + "location": "features/step_definitions/steps.rb:24" + }, + "result": { + "status": "failed", + "error_message": "failed in before hook (RuntimeError)\n./features/step_definitions/steps.rb:25:in `Before'", + "duration": 141000 + } + } + ] + }, + { + "id": "rule-sample;with-a-failing-after-hook", + "keyword": "Example", + "name": "With a failing after hook", + "description": "", + "line": 41, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this will pass", + "line": 42, + "match": { + "location": "features/step_definitions/steps.rb:1" + }, + "result": { + "status": "passed", + "duration": 12000 + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 43, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "passed", + "duration": 7000 + } + }, + { + "keyword": "Then ", + "name": "some results should be there", + "line": 44, + "match": { + "location": "features/step_definitions/steps.rb:12" + }, + "result": { + "status": "passed", + "duration": 30000 + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + }, + { + "name": "@failing_after_hook", + "line": 40 + } + ], + "after": [ + { + "match": { + "location": "features/step_definitions/steps.rb:28" + }, + "result": { + "status": "failed", + "error_message": "failed in after hook (RuntimeError)\n./features/step_definitions/steps.rb:29:in `After'", + "duration": 129000 + } + } + ] + } + ] + } +] diff --git a/internal/test/fixtures/cucumber/undefined.json b/internal/test/fixtures/cucumber/undefined.json new file mode 100644 index 0000000..c264248 --- /dev/null +++ b/internal/test/fixtures/cucumber/undefined.json @@ -0,0 +1,71 @@ +[ + { + "id": "rule-sample", + "uri": "features/rule.feature", + "keyword": "Feature", + "name": "Rule Sample", + "description": "", + "line": 2, + "tags": [ + { + "name": "@feature_tag", + "line": 1 + } + ], + "elements": [ + { + "id": "rule-sample;an-undefined-example", + "keyword": "Example", + "name": "An undefined example", + "description": "", + "line": 28, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "this is undefined", + "line": 29, + "match": { + "location": "features/rule.feature:29" + }, + "result": { + "status": "undefined" + } + }, + { + "keyword": "When ", + "name": "I do an action", + "line": 30, + "match": { + "location": "features/step_definitions/steps.rb:9" + }, + "result": { + "status": "skipped" + } + }, + { + "keyword": "Then ", + "name": "nothing should happen", + "line": 31, + "match": { + "location": "features/rule.feature:31" + }, + "result": { + "status": "undefined" + } + } + ], + "tags": [ + { + "name": "@feature_tag", + "line": 1 + }, + { + "name": "@rule_tag", + "line": 4 + } + ] + } + ] + } +] diff --git a/internal/test/fixtures/cypress.xml b/internal/test/fixtures/cypress.xml new file mode 100644 index 0000000..684c7d5 --- /dev/null +++ b/internal/test/fixtures/cypress.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + ' to have text 'Walk the cat', but the text was 'Walk the dog' + at Context.eval (webpack:///./cypress/e2e/todo-one.cy.js:59:35) + + + expected - actual + + -'Walk the dog' + +'Walk the cat' + ]]> + line 1 + line 2 + line 3 + + + + + + line 1 + line 2 + line 3 + + + + + (https://example.cypress.io/__cypress/runner/cypress_runner.js:164958:30) + at PassThroughHandlerContext.finallyHandler (https://example.cypress.io/__cypress/runner/cypress_runner.js:7872:23) + at PassThroughHandlerContext.tryCatcher (https://example.cypress.io/__cypress/runner/cypress_runner.js:11318:23) + at Promise._settlePromiseFromHandler (https://example.cypress.io/__cypress/runner/cypress_runner.js:9253:31) + at Promise._settlePromise (https://example.cypress.io/__cypress/runner/cypress_runner.js:9310:18) + at Promise._settlePromise0 (https://example.cypress.io/__cypress/runner/cypress_runner.js:9355:10) + at Promise._settlePromises (https://example.cypress.io/__cypress/runner/cypress_runner.js:9435:18) + at _drainQueueStep (https://example.cypress.io/__cypress/runner/cypress_runner.js:6025:12) + at _drainQueue (https://example.cypress.io/__cypress/runner/cypress_runner.js:6018:9) + at ../../node_modules/bluebird/js/release/async.js.Async._drainQueues (https://example.cypress.io/__cypress/runner/cypress_runner.js:6034:5) + at Async.drainQueues (https://example.cypress.io/__cypress/runner/cypress_runner.js:5904:14)]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/test/fixtures/exunit.xml b/internal/test/fixtures/exunit.xml new file mode 100644 index 0000000..b91bc44 --- /dev/null +++ b/internal/test/fixtures/exunit.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + 1) test throws an exception (ExunitexampleWeb.ExceptionTest) + test/exunitexample_web/views/exception_test.exs:7 + ** (throw) "some exception" + code: throw "some exception" + stacktrace: + test/exunitexample_web/views/exception_test.exs:8: (test) + + + + + + + + + + 1) test is failing (ExunitexampleWeb.FailingTest) + test/exunitexample_web/views/failing_test.exs:7 + Expected truthy, got false + code: assert false + stacktrace: + test/exunitexample_web/views/failing_test.exs:8: (test) + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/test/fixtures/filenames/nested/$ @=:+{}[]^><~#|.txt b/internal/test/fixtures/filenames/nested/$ @=:+{}[]^><~#|.txt new file mode 100644 index 0000000..e69de29 diff --git a/internal/test/fixtures/filenames/nested/**.txt b/internal/test/fixtures/filenames/nested/**.txt new file mode 100644 index 0000000..e69de29 diff --git a/internal/test/fixtures/filenames/nested/*.txt b/internal/test/fixtures/filenames/nested/*.txt new file mode 100644 index 0000000..e69de29 diff --git a/internal/test/fixtures/filenames/nested/?.txt b/internal/test/fixtures/filenames/nested/?.txt new file mode 100644 index 0000000..e69de29 diff --git a/internal/test/fixtures/filenames/nested/[].txt b/internal/test/fixtures/filenames/nested/[].txt new file mode 100644 index 0000000..e69de29 diff --git "a/internal/test/fixtures/filenames/nested/\\.txt" "b/internal/test/fixtures/filenames/nested/\\.txt" new file mode 100644 index 0000000..e69de29 diff --git a/internal/test/fixtures/ginkgo.json b/internal/test/fixtures/ginkgo.json new file mode 100644 index 0000000..54e1f0f --- /dev/null +++ b/internal/test/fixtures/ginkgo.json @@ -0,0 +1,3534 @@ +[ + { + "SuitePath": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1", + "SuiteDescription": "Pkg1 Suite", + "SuiteLabels": [], + "SuiteSucceeded": false, + "SuiteHasProgrammaticFocus": false, + "SpecialSuiteFailureReasons": null, + "PreRunStats": { + "TotalSpecs": 17, + "SpecsThatWillRun": 16 + }, + "StartTime": "2022-12-14T13:20:51.079027-05:00", + "EndTime": "2022-12-14T13:20:55.587717-05:00", + "RunTime": 4508667167, + "SuiteConfig": { + "RandomSeed": 1671042050, + "RandomizeAllSpecs": false, + "FocusStrings": null, + "SkipStrings": null, + "FocusFiles": null, + "SkipFiles": null, + "LabelFilter": "", + "FailOnPending": false, + "FailFast": false, + "FlakeAttempts": 2, + "DryRun": false, + "PollProgressAfter": 0, + "PollProgressInterval": 0, + "Timeout": 3599078667125, + "OutputInterceptorMode": "", + "SourceRoots": null, + "GracePeriod": 30000000000, + "ParallelProcess": 1, + "ParallelTotal": 1, + "ParallelHost": "" + }, + "SpecReports": [ + { + "ContainerHierarchyTexts": [], + "ContainerHierarchyLocations": [], + "ContainerHierarchyLabels": [], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 53 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails at the top-level", + "State": "failed", + "StartTime": "2022-12-14T13:20:51.079143-05:00", + "EndTime": "2022-12-14T13:20:51.079458-05:00", + "RunTime": 314792, + "ParallelProcess": 1, + "Failure": { + "Message": "Expected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 54, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func3()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:54 +0xd4" + }, + "TimelineLocation": { + "Order": 6, + "Time": "2022-12-14T13:20:51.079452-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 53 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 2, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "AdditionalFailures": [ + { + "State": "failed", + "Failure": { + "Message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 54, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func3()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:54 +0xd4" + }, + "TimelineLocation": { + "Order": 2, + "Time": "2022-12-14T13:20:51.079359-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 53 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + } + } + ], + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 53 + }, + "TimelineLocation": { + "Order": 1, + "Time": "2022-12-14T13:20:51.079145-05:00" + }, + "Message": "fails at the top-level", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 53 + }, + "TimelineLocation": { + "Order": 3, + "Time": "2022-12-14T13:20:51.079366-05:00" + }, + "Message": "fails at the top-level", + "Duration": 220666, + "NodeType": "It" + }, + { + "SpecEventType": "Retry", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 4, + "Time": "2022-12-14T13:20:51.079372-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 53 + }, + "TimelineLocation": { + "Order": 5, + "Time": "2022-12-14T13:20:51.079374-05:00" + }, + "Message": "fails at the top-level", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 53 + }, + "TimelineLocation": { + "Order": 7, + "Time": "2022-12-14T13:20:51.079457-05:00" + }, + "Message": "fails at the top-level", + "Duration": 83000, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Bar" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 10 + } + ], + "ContainerHierarchyLabels": [ + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 15 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes outside a context", + "State": "passed", + "StartTime": "2022-12-14T13:20:51.079601-05:00", + "EndTime": "2022-12-14T13:20:51.079629-05:00", + "RunTime": 27833, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 8, + "Time": "2022-12-14T13:20:51.079603-05:00" + }, + "Message": "Bar", + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 10, + "Time": "2022-12-14T13:20:51.079614-05:00" + }, + "Message": "Bar", + "Duration": 11667, + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 15 + }, + "TimelineLocation": { + "Order": 11, + "Time": "2022-12-14T13:20:51.079615-05:00" + }, + "Message": "passes outside a context", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 15 + }, + "TimelineLocation": { + "Order": 13, + "Time": "2022-12-14T13:20:51.079628-05:00" + }, + "Message": "passes outside a context", + "Duration": 12250, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Bar" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 10 + } + ], + "ContainerHierarchyLabels": [ + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 19 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails outside a context", + "State": "failed", + "StartTime": "2022-12-14T13:20:51.079639-05:00", + "EndTime": "2022-12-14T13:20:51.079801-05:00", + "RunTime": 161666, + "ParallelProcess": 1, + "Failure": { + "Message": "Expected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 20, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func1.3()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:20 +0xd4" + }, + "TimelineLocation": { + "Order": 25, + "Time": "2022-12-14T13:20:51.079796-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 19 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 2, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "AdditionalFailures": [ + { + "State": "failed", + "Failure": { + "Message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 20, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func1.3()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:20 +0xd4" + }, + "TimelineLocation": { + "Order": 18, + "Time": "2022-12-14T13:20:51.079716-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 19 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + } + } + ], + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 14, + "Time": "2022-12-14T13:20:51.07964-05:00" + }, + "Message": "Bar", + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 16, + "Time": "2022-12-14T13:20:51.079645-05:00" + }, + "Message": "Bar", + "Duration": 4667, + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 19 + }, + "TimelineLocation": { + "Order": 17, + "Time": "2022-12-14T13:20:51.079646-05:00" + }, + "Message": "fails outside a context", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 19 + }, + "TimelineLocation": { + "Order": 19, + "Time": "2022-12-14T13:20:51.079719-05:00" + }, + "Message": "fails outside a context", + "Duration": 73500, + "NodeType": "It" + }, + { + "SpecEventType": "Retry", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 20, + "Time": "2022-12-14T13:20:51.079721-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 21, + "Time": "2022-12-14T13:20:51.079724-05:00" + }, + "Message": "Bar", + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 23, + "Time": "2022-12-14T13:20:51.079727-05:00" + }, + "Message": "Bar", + "Duration": 3291, + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 19 + }, + "TimelineLocation": { + "Order": 24, + "Time": "2022-12-14T13:20:51.079728-05:00" + }, + "Message": "fails outside a context", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 19 + }, + "TimelineLocation": { + "Order": 26, + "Time": "2022-12-14T13:20:51.0798-05:00" + }, + "Message": "fails outside a context", + "Duration": 72625, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Bar", + "within a context" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 10 + }, + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 23 + } + ], + "ContainerHierarchyLabels": [ + [], + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 28 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes", + "State": "passed", + "StartTime": "2022-12-14T13:20:51.079909-05:00", + "EndTime": "2022-12-14T13:20:51.079944-05:00", + "RunTime": 35416, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 27, + "Time": "2022-12-14T13:20:51.079912-05:00" + }, + "Message": "Bar", + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 29, + "Time": "2022-12-14T13:20:51.079925-05:00" + }, + "Message": "Bar", + "Duration": 12792, + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 30, + "Time": "2022-12-14T13:20:51.079926-05:00" + }, + "Message": "within a context", + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 32, + "Time": "2022-12-14T13:20:51.07993-05:00" + }, + "Message": "within a context", + "Duration": 3875, + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 28 + }, + "TimelineLocation": { + "Order": 33, + "Time": "2022-12-14T13:20:51.079931-05:00" + }, + "Message": "passes", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 28 + }, + "TimelineLocation": { + "Order": 35, + "Time": "2022-12-14T13:20:51.079943-05:00" + }, + "Message": "passes", + "Duration": 12542, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Bar", + "within a context" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 10 + }, + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 23 + } + ], + "ContainerHierarchyLabels": [ + [], + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 32 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails", + "State": "failed", + "StartTime": "2022-12-14T13:20:51.079952-05:00", + "EndTime": "2022-12-14T13:20:51.080139-05:00", + "RunTime": 187750, + "ParallelProcess": 1, + "Failure": { + "Message": "Expected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 33, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func1.4.3()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:33 +0xd4" + }, + "TimelineLocation": { + "Order": 53, + "Time": "2022-12-14T13:20:51.080135-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 32 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 2, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "AdditionalFailures": [ + { + "State": "failed", + "Failure": { + "Message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 33, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func1.4.3()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:33 +0xd4" + }, + "TimelineLocation": { + "Order": 43, + "Time": "2022-12-14T13:20:51.080052-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 32 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + } + } + ], + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 36, + "Time": "2022-12-14T13:20:51.079953-05:00" + }, + "Message": "Bar", + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 38, + "Time": "2022-12-14T13:20:51.07996-05:00" + }, + "Message": "Bar", + "Duration": 6792, + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 39, + "Time": "2022-12-14T13:20:51.079961-05:00" + }, + "Message": "within a context", + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 41, + "Time": "2022-12-14T13:20:51.079965-05:00" + }, + "Message": "within a context", + "Duration": 4375, + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 32 + }, + "TimelineLocation": { + "Order": 42, + "Time": "2022-12-14T13:20:51.079965-05:00" + }, + "Message": "fails", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 32 + }, + "TimelineLocation": { + "Order": 44, + "Time": "2022-12-14T13:20:51.080056-05:00" + }, + "Message": "fails", + "Duration": 90458, + "NodeType": "It" + }, + { + "SpecEventType": "Retry", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 45, + "Time": "2022-12-14T13:20:51.080058-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 46, + "Time": "2022-12-14T13:20:51.080059-05:00" + }, + "Message": "Bar", + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 48, + "Time": "2022-12-14T13:20:51.080063-05:00" + }, + "Message": "Bar", + "Duration": 3375, + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 49, + "Time": "2022-12-14T13:20:51.080063-05:00" + }, + "Message": "within a context", + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 51, + "Time": "2022-12-14T13:20:51.080066-05:00" + }, + "Message": "within a context", + "Duration": 3125, + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 32 + }, + "TimelineLocation": { + "Order": 52, + "Time": "2022-12-14T13:20:51.080067-05:00" + }, + "Message": "fails", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 32 + }, + "TimelineLocation": { + "Order": 54, + "Time": "2022-12-14T13:20:51.080138-05:00" + }, + "Message": "fails", + "Duration": 71459, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Bar", + "failing before each" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 10 + }, + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 37 + } + ], + "ContainerHierarchyLabels": [ + [], + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 42 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails even with the test passing", + "State": "failed", + "StartTime": "2022-12-14T13:20:51.080202-05:00", + "EndTime": "2022-12-14T13:20:51.080403-05:00", + "RunTime": 201083, + "ParallelProcess": 1, + "Failure": { + "Message": "Expected\n \u003cint\u003e: 1\nto equal\n \u003cint\u003e: 2", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 39, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func1.5.1()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:39 +0x90" + }, + "TimelineLocation": { + "Order": 66, + "Time": "2022-12-14T13:20:51.080396-05:00" + }, + "FailureNodeContext": "in-container", + "FailureNodeType": "BeforeEach", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 38 + }, + "FailureNodeContainerIndex": 1, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 2, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "AdditionalFailures": [ + { + "State": "failed", + "Failure": { + "Message": "Failure recorded during attempt 1:\nExpected\n \u003cint\u003e: 1\nto equal\n \u003cint\u003e: 2", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 39, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func1.5.1()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go:39 +0x90" + }, + "TimelineLocation": { + "Order": 59, + "Time": "2022-12-14T13:20:51.080281-05:00" + }, + "FailureNodeContext": "in-container", + "FailureNodeType": "BeforeEach", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 38 + }, + "FailureNodeContainerIndex": 1, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + } + } + ], + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 55, + "Time": "2022-12-14T13:20:51.080205-05:00" + }, + "Message": "Bar", + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 57, + "Time": "2022-12-14T13:20:51.080212-05:00" + }, + "Message": "Bar", + "Duration": 6500, + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 38 + }, + "TimelineLocation": { + "Order": 58, + "Time": "2022-12-14T13:20:51.080212-05:00" + }, + "Message": "failing before each", + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 38 + }, + "TimelineLocation": { + "Order": 60, + "Time": "2022-12-14T13:20:51.080284-05:00" + }, + "Message": "failing before each", + "Duration": 71500, + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Retry", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 61, + "Time": "2022-12-14T13:20:51.080286-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 62, + "Time": "2022-12-14T13:20:51.080292-05:00" + }, + "Message": "Bar", + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 64, + "Time": "2022-12-14T13:20:51.080295-05:00" + }, + "Message": "Bar", + "Duration": 3209, + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 38 + }, + "TimelineLocation": { + "Order": 65, + "Time": "2022-12-14T13:20:51.080295-05:00" + }, + "Message": "failing before each", + "NodeType": "BeforeEach" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 38 + }, + "TimelineLocation": { + "Order": 67, + "Time": "2022-12-14T13:20:51.080401-05:00" + }, + "Message": "failing before each", + "Duration": 105750, + "NodeType": "BeforeEach" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Foo" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 13 + } + ], + "ContainerHierarchyLabels": [ + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 14 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes outside a context", + "State": "pending", + "StartTime": "2022-12-14T13:20:51.080478-05:00", + "EndTime": "0001-01-01T00:00:00Z", + "RunTime": 0, + "ParallelProcess": 1, + "NumAttempts": 0, + "MaxFlakeAttempts": 0, + "MaxMustPassRepeatedly": 0 + }, + { + "ContainerHierarchyTexts": [ + "Foo" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 13 + } + ], + "ContainerHierarchyLabels": [ + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 18 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails while skipped", + "State": "skipped", + "StartTime": "2022-12-14T13:20:51.080484-05:00", + "EndTime": "2022-12-14T13:20:51.080556-05:00", + "RunTime": 71125, + "ParallelProcess": 1, + "Failure": { + "Message": "for a reason", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 19, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func4.2()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go:19 +0x34" + }, + "TimelineLocation": { + "Order": 69, + "Time": "2022-12-14T13:20:51.080551-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 18 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 18 + }, + "TimelineLocation": { + "Order": 68, + "Time": "2022-12-14T13:20:51.080486-05:00" + }, + "Message": "fails while skipped", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 18 + }, + "TimelineLocation": { + "Order": 70, + "Time": "2022-12-14T13:20:51.080554-05:00" + }, + "Message": "fails while skipped", + "Duration": 68167, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Foo" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 13 + } + ], + "ContainerHierarchyLabels": [ + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 23 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes while skipped", + "State": "skipped", + "StartTime": "2022-12-14T13:20:51.080563-05:00", + "EndTime": "2022-12-14T13:20:51.080615-05:00", + "RunTime": 52000, + "ParallelProcess": 1, + "Failure": { + "Message": "for a reason", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 24, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func4.3()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go:24 +0x34" + }, + "TimelineLocation": { + "Order": 72, + "Time": "2022-12-14T13:20:51.08061-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 23 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 23 + }, + "TimelineLocation": { + "Order": 71, + "Time": "2022-12-14T13:20:51.080564-05:00" + }, + "Message": "passes while skipped", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 23 + }, + "TimelineLocation": { + "Order": 73, + "Time": "2022-12-14T13:20:51.080614-05:00" + }, + "Message": "passes while skipped", + "Duration": 49958, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Foo", + "within a context" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 13 + }, + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 28 + } + ], + "ContainerHierarchyLabels": [ + [], + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 29 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes", + "State": "passed", + "StartTime": "2022-12-14T13:20:51.080626-05:00", + "EndTime": "2022-12-14T13:20:51.080635-05:00", + "RunTime": 9208, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 29 + }, + "TimelineLocation": { + "Order": 74, + "Time": "2022-12-14T13:20:51.080627-05:00" + }, + "Message": "passes", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 29 + }, + "TimelineLocation": { + "Order": 76, + "Time": "2022-12-14T13:20:51.080634-05:00" + }, + "Message": "passes", + "Duration": 7459, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Foo", + "within a context" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 13 + }, + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 28 + } + ], + "ContainerHierarchyLabels": [ + [], + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 33 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails", + "State": "failed", + "StartTime": "2022-12-14T13:20:51.08064-05:00", + "EndTime": "2022-12-14T13:20:51.080805-05:00", + "RunTime": 164750, + "ParallelProcess": 1, + "Failure": { + "Message": "Expected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 34, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func4.4.2()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go:34 +0xd4" + }, + "TimelineLocation": { + "Order": 82, + "Time": "2022-12-14T13:20:51.0808-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 33 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 2, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "AdditionalFailures": [ + { + "State": "failed", + "Failure": { + "Message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 34, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func4.4.2()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go:34 +0xd4" + }, + "TimelineLocation": { + "Order": 78, + "Time": "2022-12-14T13:20:51.080711-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 33 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + } + } + ], + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 33 + }, + "TimelineLocation": { + "Order": 77, + "Time": "2022-12-14T13:20:51.080641-05:00" + }, + "Message": "fails", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 33 + }, + "TimelineLocation": { + "Order": 79, + "Time": "2022-12-14T13:20:51.080715-05:00" + }, + "Message": "fails", + "Duration": 73750, + "NodeType": "It" + }, + { + "SpecEventType": "Retry", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 80, + "Time": "2022-12-14T13:20:51.080716-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 33 + }, + "TimelineLocation": { + "Order": 81, + "Time": "2022-12-14T13:20:51.080719-05:00" + }, + "Message": "fails", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 33 + }, + "TimelineLocation": { + "Order": 83, + "Time": "2022-12-14T13:20:51.080804-05:00" + }, + "Message": "fails", + "Duration": 84542, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Foo", + "Fooing with different args" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 13 + }, + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 38 + } + ], + "ContainerHierarchyLabels": [ + [], + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 41 + }, + "LeafNodeLabels": [], + "LeafNodeText": "When a", + "State": "passed", + "StartTime": "2022-12-14T13:20:51.080859-05:00", + "EndTime": "2022-12-14T13:20:51.080934-05:00", + "RunTime": 74334, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 41 + }, + "TimelineLocation": { + "Order": 84, + "Time": "2022-12-14T13:20:51.08086-05:00" + }, + "Message": "When a", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 41 + }, + "TimelineLocation": { + "Order": 86, + "Time": "2022-12-14T13:20:51.080933-05:00" + }, + "Message": "When a", + "Duration": 72750, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Foo", + "Fooing with different args" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 13 + }, + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 38 + } + ], + "ContainerHierarchyLabels": [ + [], + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 42 + }, + "LeafNodeLabels": [], + "LeafNodeText": "When b", + "State": "passed", + "StartTime": "2022-12-14T13:20:51.080943-05:00", + "EndTime": "2022-12-14T13:20:51.080951-05:00", + "RunTime": 7750, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 42 + }, + "TimelineLocation": { + "Order": 87, + "Time": "2022-12-14T13:20:51.080944-05:00" + }, + "Message": "When b", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 42 + }, + "TimelineLocation": { + "Order": 89, + "Time": "2022-12-14T13:20:51.08095-05:00" + }, + "Message": "When b", + "Duration": 6541, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Foo", + "Fooing with different args" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 13 + }, + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 38 + } + ], + "ContainerHierarchyLabels": [ + [], + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 43 + }, + "LeafNodeLabels": [], + "LeafNodeText": "When c", + "State": "passed", + "StartTime": "2022-12-14T13:20:51.080955-05:00", + "EndTime": "2022-12-14T13:20:51.080961-05:00", + "RunTime": 6292, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 43 + }, + "TimelineLocation": { + "Order": 90, + "Time": "2022-12-14T13:20:51.080955-05:00" + }, + "Message": "When c", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 43 + }, + "TimelineLocation": { + "Order": 92, + "Time": "2022-12-14T13:20:51.080961-05:00" + }, + "Message": "When c", + "Duration": 5208, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [], + "ContainerHierarchyLocations": [], + "ContainerHierarchyLabels": [], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 52 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails slowly at the top-level", + "State": "failed", + "StartTime": "2022-12-14T13:20:51.080965-05:00", + "EndTime": "2022-12-14T13:20:54.084629-05:00", + "RunTime": 3003648416, + "ParallelProcess": 1, + "Failure": { + "Message": "Expected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 54, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func6()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go:54 +0xdc" + }, + "TimelineLocation": { + "Order": 98, + "Time": "2022-12-14T13:20:54.084571-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 52 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 2, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "AdditionalFailures": [ + { + "State": "failed", + "Failure": { + "Message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 54, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg1_test.glob..func6()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go:54 +0xdc" + }, + "TimelineLocation": { + "Order": 94, + "Time": "2022-12-14T13:20:52.582619-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 52 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + } + } + ], + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 52 + }, + "TimelineLocation": { + "Order": 93, + "Time": "2022-12-14T13:20:51.080966-05:00" + }, + "Message": "fails slowly at the top-level", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 52 + }, + "TimelineLocation": { + "Order": 95, + "Time": "2022-12-14T13:20:52.58265-05:00" + }, + "Message": "fails slowly at the top-level", + "Duration": 1501676833, + "NodeType": "It" + }, + { + "SpecEventType": "Retry", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 96, + "Time": "2022-12-14T13:20:52.582678-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 52 + }, + "TimelineLocation": { + "Order": 97, + "Time": "2022-12-14T13:20:52.582686-05:00" + }, + "Message": "fails slowly at the top-level", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 52 + }, + "TimelineLocation": { + "Order": 99, + "Time": "2022-12-14T13:20:54.084604-05:00" + }, + "Message": "fails slowly at the top-level", + "Duration": 1501909708, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [], + "ContainerHierarchyLocations": [], + "ContainerHierarchyLabels": [], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 48 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes at the top-level", + "State": "passed", + "StartTime": "2022-12-14T13:20:54.085975-05:00", + "EndTime": "2022-12-14T13:20:54.086236-05:00", + "RunTime": 261333, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "CapturedGinkgoWriterOutput": "passing at the top-level, tada!", + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 48 + }, + "TimelineLocation": { + "Order": 100, + "Time": "2022-12-14T13:20:54.085983-05:00" + }, + "Message": "passes at the top-level", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/bar_test.go", + "LineNumber": 48 + }, + "TimelineLocation": { + "Offset": 31, + "Order": 102, + "Time": "2022-12-14T13:20:54.086229-05:00" + }, + "Message": "passes at the top-level", + "Duration": 246208, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [], + "ContainerHierarchyLocations": [], + "ContainerHierarchyLabels": [], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 47 + }, + "LeafNodeLabels": [ + "has", + "labels" + ], + "LeafNodeText": "passes slowly at the top-level", + "State": "passed", + "StartTime": "2022-12-14T13:20:54.086315-05:00", + "EndTime": "2022-12-14T13:20:55.587613-05:00", + "RunTime": 1501290417, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 47 + }, + "TimelineLocation": { + "Order": 103, + "Time": "2022-12-14T13:20:54.086319-05:00" + }, + "Message": "passes slowly at the top-level", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 47 + }, + "TimelineLocation": { + "Order": 105, + "Time": "2022-12-14T13:20:55.587595-05:00" + }, + "Message": "passes slowly at the top-level", + "Duration": 1501268292, + "NodeType": "It" + } + ] + } + ] + }, + { + "SuitePath": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2", + "SuiteDescription": "Pkg2 Suite", + "SuiteLabels": [], + "SuiteSucceeded": false, + "SuiteHasProgrammaticFocus": false, + "SpecialSuiteFailureReasons": null, + "PreRunStats": { + "TotalSpecs": 14, + "SpecsThatWillRun": 14 + }, + "StartTime": "2022-12-14T13:20:55.749543-05:00", + "EndTime": "2022-12-14T13:20:55.756371-05:00", + "RunTime": 6827833, + "SuiteConfig": { + "RandomSeed": 1671042050, + "RandomizeAllSpecs": false, + "FocusStrings": null, + "SkipStrings": null, + "FocusFiles": null, + "SkipFiles": null, + "LabelFilter": "", + "FailOnPending": false, + "FailFast": false, + "FlakeAttempts": 2, + "DryRun": false, + "PollProgressAfter": 0, + "PollProgressInterval": 0, + "Timeout": 3594425193875, + "OutputInterceptorMode": "", + "SourceRoots": null, + "GracePeriod": 30000000000, + "ParallelProcess": 1, + "ParallelTotal": 1, + "ParallelHost": "" + }, + "SpecReports": [ + { + "ContainerHierarchyTexts": [], + "ContainerHierarchyLocations": [], + "ContainerHierarchyLabels": [], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 34 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails at the top-level", + "State": "failed", + "StartTime": "2022-12-14T13:20:55.749969-05:00", + "EndTime": "2022-12-14T13:20:55.750871-05:00", + "RunTime": 902250, + "ParallelProcess": 1, + "Failure": { + "Message": "Expected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 35, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func3()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:35 +0xd4" + }, + "TimelineLocation": { + "Order": 6, + "Time": "2022-12-14T13:20:55.750864-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 34 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 2, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "AdditionalFailures": [ + { + "State": "failed", + "Failure": { + "Message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 35, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func3()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:35 +0xd4" + }, + "TimelineLocation": { + "Order": 2, + "Time": "2022-12-14T13:20:55.750652-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 34 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + } + } + ], + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 34 + }, + "TimelineLocation": { + "Order": 1, + "Time": "2022-12-14T13:20:55.749973-05:00" + }, + "Message": "fails at the top-level", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 34 + }, + "TimelineLocation": { + "Order": 3, + "Time": "2022-12-14T13:20:55.75066-05:00" + }, + "Message": "fails at the top-level", + "Duration": 687125, + "NodeType": "It" + }, + { + "SpecEventType": "Retry", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 4, + "Time": "2022-12-14T13:20:55.75067-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 34 + }, + "TimelineLocation": { + "Order": 5, + "Time": "2022-12-14T13:20:55.750673-05:00" + }, + "Message": "fails at the top-level", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 34 + }, + "TimelineLocation": { + "Order": 7, + "Time": "2022-12-14T13:20:55.750869-05:00" + }, + "Message": "fails at the top-level", + "Duration": 196125, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Bar" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 10 + } + ], + "ContainerHierarchyLabels": [ + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 11 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes outside a context", + "State": "passed", + "StartTime": "2022-12-14T13:20:55.751146-05:00", + "EndTime": "2022-12-14T13:20:55.75117-05:00", + "RunTime": 24333, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 8, + "Time": "2022-12-14T13:20:55.751148-05:00" + }, + "Message": "passes outside a context", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 11 + }, + "TimelineLocation": { + "Order": 10, + "Time": "2022-12-14T13:20:55.751168-05:00" + }, + "Message": "passes outside a context", + "Duration": 20041, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Bar" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 10 + } + ], + "ContainerHierarchyLabels": [ + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 15 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails outside a context", + "State": "failed", + "StartTime": "2022-12-14T13:20:55.751191-05:00", + "EndTime": "2022-12-14T13:20:55.751562-05:00", + "RunTime": 370250, + "ParallelProcess": 1, + "Failure": { + "Message": "Expected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 16, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func1.2()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:16 +0xd4" + }, + "TimelineLocation": { + "Order": 16, + "Time": "2022-12-14T13:20:55.751557-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 15 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 2, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "AdditionalFailures": [ + { + "State": "failed", + "Failure": { + "Message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 16, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func1.2()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:16 +0xd4" + }, + "TimelineLocation": { + "Order": 12, + "Time": "2022-12-14T13:20:55.751381-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 15 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + } + } + ], + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 15 + }, + "TimelineLocation": { + "Order": 11, + "Time": "2022-12-14T13:20:55.751193-05:00" + }, + "Message": "fails outside a context", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 15 + }, + "TimelineLocation": { + "Order": 13, + "Time": "2022-12-14T13:20:55.751383-05:00" + }, + "Message": "fails outside a context", + "Duration": 190000, + "NodeType": "It" + }, + { + "SpecEventType": "Retry", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 14, + "Time": "2022-12-14T13:20:55.751387-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 15 + }, + "TimelineLocation": { + "Order": 15, + "Time": "2022-12-14T13:20:55.751389-05:00" + }, + "Message": "fails outside a context", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 15 + }, + "TimelineLocation": { + "Order": 17, + "Time": "2022-12-14T13:20:55.75156-05:00" + }, + "Message": "fails outside a context", + "Duration": 170583, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Bar", + "within a context" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 10 + }, + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 19 + } + ], + "ContainerHierarchyLabels": [ + [], + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 20 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes", + "State": "passed", + "StartTime": "2022-12-14T13:20:55.751959-05:00", + "EndTime": "2022-12-14T13:20:55.751991-05:00", + "RunTime": 31375, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 20 + }, + "TimelineLocation": { + "Order": 18, + "Time": "2022-12-14T13:20:55.751962-05:00" + }, + "Message": "passes", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 20 + }, + "TimelineLocation": { + "Order": 20, + "Time": "2022-12-14T13:20:55.751988-05:00" + }, + "Message": "passes", + "Duration": 25917, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Bar", + "within a context" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 10 + }, + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 19 + } + ], + "ContainerHierarchyLabels": [ + [], + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 24 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails", + "State": "failed", + "StartTime": "2022-12-14T13:20:55.752172-05:00", + "EndTime": "2022-12-14T13:20:55.752562-05:00", + "RunTime": 390125, + "ParallelProcess": 1, + "Failure": { + "Message": "Expected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 25, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func1.3.2()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:25 +0xd4" + }, + "TimelineLocation": { + "Order": 26, + "Time": "2022-12-14T13:20:55.752558-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 24 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 2, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "AdditionalFailures": [ + { + "State": "failed", + "Failure": { + "Message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Bar: not arg\nto equal\n \u003cstring\u003e: Bar: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 25, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func1.3.2()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go:25 +0xd4" + }, + "TimelineLocation": { + "Order": 22, + "Time": "2022-12-14T13:20:55.752369-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 24 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + } + } + ], + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 21, + "Time": "2022-12-14T13:20:55.752174-05:00" + }, + "Message": "fails", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 23, + "Time": "2022-12-14T13:20:55.752372-05:00" + }, + "Message": "fails", + "Duration": 197375, + "NodeType": "It" + }, + { + "SpecEventType": "Retry", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 24, + "Time": "2022-12-14T13:20:55.752376-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 25, + "Time": "2022-12-14T13:20:55.752378-05:00" + }, + "Message": "fails", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 27, + "Time": "2022-12-14T13:20:55.75256-05:00" + }, + "Message": "fails", + "Duration": 181917, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Foo" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 13 + } + ], + "ContainerHierarchyLabels": [ + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 14 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails then passes", + "State": "passed", + "StartTime": "2022-12-14T13:20:55.752989-05:00", + "EndTime": "2022-12-14T13:20:55.753942-05:00", + "RunTime": 952667, + "ParallelProcess": 1, + "NumAttempts": 2, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "AdditionalFailures": [ + { + "State": "failed", + "Failure": { + "Message": "Failure recorded during attempt 1:\nuh oh", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 18, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func4.1()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:18 +0x124" + }, + "TimelineLocation": { + "Order": 29, + "Time": "2022-12-14T13:20:55.753391-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 14 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + } + } + ], + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 14 + }, + "TimelineLocation": { + "Order": 28, + "Time": "2022-12-14T13:20:55.752991-05:00" + }, + "Message": "fails then passes", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 14 + }, + "TimelineLocation": { + "Order": 30, + "Time": "2022-12-14T13:20:55.753395-05:00" + }, + "Message": "fails then passes", + "Duration": 403250, + "NodeType": "It" + }, + { + "SpecEventType": "Retry", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 31, + "Time": "2022-12-14T13:20:55.7534-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 14 + }, + "TimelineLocation": { + "Order": 32, + "Time": "2022-12-14T13:20:55.753402-05:00" + }, + "Message": "fails then passes", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 14 + }, + "TimelineLocation": { + "Order": 34, + "Time": "2022-12-14T13:20:55.753936-05:00" + }, + "Message": "fails then passes", + "Duration": 534000, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Foo" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 13 + } + ], + "ContainerHierarchyLabels": [ + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 24 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes then fails", + "State": "failed", + "StartTime": "2022-12-14T13:20:55.753993-05:00", + "EndTime": "2022-12-14T13:20:55.75441-05:00", + "RunTime": 417125, + "ParallelProcess": 1, + "Failure": { + "Message": "uh oh", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 30, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func4.2()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:30 +0x44" + }, + "TimelineLocation": { + "Order": 40, + "Time": "2022-12-14T13:20:55.754398-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 24 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 2, + "MaxFlakeAttempts": 0, + "MaxMustPassRepeatedly": 2, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 35, + "Time": "2022-12-14T13:20:55.753996-05:00" + }, + "Message": "passes then fails", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 37, + "Time": "2022-12-14T13:20:55.754013-05:00" + }, + "Message": "passes then fails", + "Duration": 17500, + "NodeType": "It" + }, + { + "SpecEventType": "Repeat", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 38, + "Time": "2022-12-14T13:20:55.754015-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 39, + "Time": "2022-12-14T13:20:55.754017-05:00" + }, + "Message": "passes then fails", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 24 + }, + "TimelineLocation": { + "Order": 41, + "Time": "2022-12-14T13:20:55.754401-05:00" + }, + "Message": "passes then fails", + "Duration": 384000, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Foo" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 13 + } + ], + "ContainerHierarchyLabels": [ + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 34 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes outside a context", + "State": "passed", + "StartTime": "2022-12-14T13:20:55.755427-05:00", + "EndTime": "2022-12-14T13:20:55.755457-05:00", + "RunTime": 30417, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 34 + }, + "TimelineLocation": { + "Order": 42, + "Time": "2022-12-14T13:20:55.75543-05:00" + }, + "Message": "passes outside a context", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 34 + }, + "TimelineLocation": { + "Order": 44, + "Time": "2022-12-14T13:20:55.755455-05:00" + }, + "Message": "passes outside a context", + "Duration": 25292, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Foo" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 13 + } + ], + "ContainerHierarchyLabels": [ + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 38 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails outside a context", + "State": "failed", + "StartTime": "2022-12-14T13:20:55.755472-05:00", + "EndTime": "2022-12-14T13:20:55.755701-05:00", + "RunTime": 229208, + "ParallelProcess": 1, + "Failure": { + "Message": "Expected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 39, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func4.4()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:39 +0xd4" + }, + "TimelineLocation": { + "Order": 50, + "Time": "2022-12-14T13:20:55.755697-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 38 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 2, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "AdditionalFailures": [ + { + "State": "failed", + "Failure": { + "Message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 39, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func4.4()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:39 +0xd4" + }, + "TimelineLocation": { + "Order": 46, + "Time": "2022-12-14T13:20:55.755595-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 38 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + } + } + ], + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 38 + }, + "TimelineLocation": { + "Order": 45, + "Time": "2022-12-14T13:20:55.755473-05:00" + }, + "Message": "fails outside a context", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 38 + }, + "TimelineLocation": { + "Order": 47, + "Time": "2022-12-14T13:20:55.755605-05:00" + }, + "Message": "fails outside a context", + "Duration": 132000, + "NodeType": "It" + }, + { + "SpecEventType": "Retry", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 48, + "Time": "2022-12-14T13:20:55.755608-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 38 + }, + "TimelineLocation": { + "Order": 49, + "Time": "2022-12-14T13:20:55.755611-05:00" + }, + "Message": "fails outside a context", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 38 + }, + "TimelineLocation": { + "Order": 51, + "Time": "2022-12-14T13:20:55.7557-05:00" + }, + "Message": "fails outside a context", + "Duration": 88292, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Foo", + "within a context" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 13 + }, + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 42 + } + ], + "ContainerHierarchyLabels": [ + [], + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 43 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes", + "State": "passed", + "StartTime": "2022-12-14T13:20:55.75577-05:00", + "EndTime": "2022-12-14T13:20:55.755782-05:00", + "RunTime": 12042, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 43 + }, + "TimelineLocation": { + "Order": 52, + "Time": "2022-12-14T13:20:55.755771-05:00" + }, + "Message": "passes", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 43 + }, + "TimelineLocation": { + "Order": 54, + "Time": "2022-12-14T13:20:55.755781-05:00" + }, + "Message": "passes", + "Duration": 9792, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [ + "Foo", + "within a context" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 13 + }, + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 42 + } + ], + "ContainerHierarchyLabels": [ + [], + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 47 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails", + "State": "failed", + "StartTime": "2022-12-14T13:20:55.755789-05:00", + "EndTime": "2022-12-14T13:20:55.756017-05:00", + "RunTime": 227958, + "ParallelProcess": 1, + "Failure": { + "Message": "Expected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 48, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func4.5.2()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:48 +0xd4" + }, + "TimelineLocation": { + "Order": 60, + "Time": "2022-12-14T13:20:55.756014-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 47 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 2, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "AdditionalFailures": [ + { + "State": "failed", + "Failure": { + "Message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 48, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func4.5.2()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:48 +0xd4" + }, + "TimelineLocation": { + "Order": 56, + "Time": "2022-12-14T13:20:55.755867-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 47 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + } + } + ], + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 47 + }, + "TimelineLocation": { + "Order": 55, + "Time": "2022-12-14T13:20:55.755789-05:00" + }, + "Message": "fails", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 47 + }, + "TimelineLocation": { + "Order": 57, + "Time": "2022-12-14T13:20:55.75587-05:00" + }, + "Message": "fails", + "Duration": 80625, + "NodeType": "It" + }, + { + "SpecEventType": "Retry", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 58, + "Time": "2022-12-14T13:20:55.755872-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 47 + }, + "TimelineLocation": { + "Order": 59, + "Time": "2022-12-14T13:20:55.755873-05:00" + }, + "Message": "fails", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 47 + }, + "TimelineLocation": { + "Order": 61, + "Time": "2022-12-14T13:20:55.756015-05:00" + }, + "Message": "fails", + "Duration": 142500, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [], + "ContainerHierarchyLocations": [], + "ContainerHierarchyLabels": [], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 57 + }, + "LeafNodeLabels": [], + "LeafNodeText": "fails at the top-level", + "State": "failed", + "StartTime": "2022-12-14T13:20:55.756113-05:00", + "EndTime": "2022-12-14T13:20:55.756283-05:00", + "RunTime": 170208, + "ParallelProcess": 1, + "Failure": { + "Message": "Expected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 58, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func6()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:58 +0xd4" + }, + "TimelineLocation": { + "Order": 67, + "Time": "2022-12-14T13:20:55.756277-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 57 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + }, + "NumAttempts": 2, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "AdditionalFailures": [ + { + "State": "failed", + "Failure": { + "Message": "Failure recorded during attempt 1:\nExpected\n \u003cstring\u003e: Foo: not arg\nto equal\n \u003cstring\u003e: Foo: arg", + "Location": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 58, + "FullStackTrace": "github.com/captain-examples/go-ginkgo/internal/pkg2_test.glob..func6()\n\t/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go:58 +0xd4" + }, + "TimelineLocation": { + "Order": 63, + "Time": "2022-12-14T13:20:55.756199-05:00" + }, + "FailureNodeContext": "leaf-node", + "FailureNodeType": "It", + "FailureNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 57 + }, + "ProgressReport": { + "LeafNodeLocation": {}, + "SpecStartTime": "0001-01-01T00:00:00Z", + "CurrentNodeLocation": {}, + "CurrentNodeStartTime": "0001-01-01T00:00:00Z", + "CurrentStepLocation": {}, + "CurrentStepStartTime": "0001-01-01T00:00:00Z", + "TimelineLocation": { + "Time": "0001-01-01T00:00:00Z" + } + } + } + } + ], + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 57 + }, + "TimelineLocation": { + "Order": 62, + "Time": "2022-12-14T13:20:55.756115-05:00" + }, + "Message": "fails at the top-level", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 57 + }, + "TimelineLocation": { + "Order": 64, + "Time": "2022-12-14T13:20:55.756201-05:00" + }, + "Message": "fails at the top-level", + "Duration": 86000, + "NodeType": "It" + }, + { + "SpecEventType": "Retry", + "CodeLocation": {}, + "TimelineLocation": { + "Order": 65, + "Time": "2022-12-14T13:20:55.756203-05:00" + }, + "Attempt": 1 + }, + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 57 + }, + "TimelineLocation": { + "Order": 66, + "Time": "2022-12-14T13:20:55.756204-05:00" + }, + "Message": "fails at the top-level", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 57 + }, + "TimelineLocation": { + "Order": 68, + "Time": "2022-12-14T13:20:55.756282-05:00" + }, + "Message": "fails at the top-level", + "Duration": 77958, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [], + "ContainerHierarchyLocations": [], + "ContainerHierarchyLabels": [], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 30 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes at the top-level", + "State": "passed", + "StartTime": "2022-12-14T13:20:55.756339-05:00", + "EndTime": "2022-12-14T13:20:55.75635-05:00", + "RunTime": 10708, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 30 + }, + "TimelineLocation": { + "Order": 69, + "Time": "2022-12-14T13:20:55.75634-05:00" + }, + "Message": "passes at the top-level", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/bar_test.go", + "LineNumber": 30 + }, + "TimelineLocation": { + "Order": 71, + "Time": "2022-12-14T13:20:55.756349-05:00" + }, + "Message": "passes at the top-level", + "Duration": 9084, + "NodeType": "It" + } + ] + }, + { + "ContainerHierarchyTexts": [], + "ContainerHierarchyLocations": [], + "ContainerHierarchyLabels": [], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 53 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes at the top-level", + "State": "passed", + "StartTime": "2022-12-14T13:20:55.756357-05:00", + "EndTime": "2022-12-14T13:20:55.756366-05:00", + "RunTime": 9625, + "ParallelProcess": 1, + "NumAttempts": 1, + "MaxFlakeAttempts": 2, + "MaxMustPassRepeatedly": 0, + "SpecEvents": [ + { + "SpecEventType": "Node", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 53 + }, + "TimelineLocation": { + "Order": 72, + "Time": "2022-12-14T13:20:55.756358-05:00" + }, + "Message": "passes at the top-level", + "NodeType": "It" + }, + { + "SpecEventType": "Node (End)", + "CodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2/foo_test.go", + "LineNumber": 53 + }, + "TimelineLocation": { + "Order": 74, + "Time": "2022-12-14T13:20:55.756366-05:00" + }, + "Message": "passes at the top-level", + "Duration": 8084, + "NodeType": "It" + } + ] + } + ] + } +] diff --git a/internal/test/fixtures/ginkgo_with_other_errors.json b/internal/test/fixtures/ginkgo_with_other_errors.json new file mode 100644 index 0000000..d1144f5 --- /dev/null +++ b/internal/test/fixtures/ginkgo_with_other_errors.json @@ -0,0 +1,110 @@ +[ + { + "SuitePath": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1", + "SuiteDescription": "Pkg1 Suite", + "SuiteLabels": [], + "SuiteSucceeded": false, + "SuiteHasProgrammaticFocus": false, + "SpecialSuiteFailureReasons": [ + "Detected pending specs and --fail-on-pending is set" + ], + "PreRunStats": { + "TotalSpecs": 1, + "SpecsThatWillRun": 0 + }, + "StartTime": "2022-12-14T14:22:04.752533-05:00", + "EndTime": "2022-12-14T14:22:09.264838-05:00", + "RunTime": 4512296166, + "SuiteConfig": { + "RandomSeed": 1671045723, + "RandomizeAllSpecs": false, + "FocusStrings": null, + "SkipStrings": null, + "FocusFiles": null, + "SkipFiles": null, + "LabelFilter": "", + "FailOnPending": true, + "FailFast": false, + "FlakeAttempts": 2, + "DryRun": false, + "PollProgressAfter": 0, + "PollProgressInterval": 0, + "Timeout": 3599122317125, + "OutputInterceptorMode": "", + "SourceRoots": null, + "GracePeriod": 30000000000, + "ParallelProcess": 1, + "ParallelTotal": 1, + "ParallelHost": "" + }, + "SpecReports": [ + { + "ContainerHierarchyTexts": [ + "Foo" + ], + "ContainerHierarchyLocations": [ + { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 13 + } + ], + "ContainerHierarchyLabels": [ + [] + ], + "LeafNodeType": "It", + "LeafNodeLocation": { + "FileName": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg1/foo_test.go", + "LineNumber": 14 + }, + "LeafNodeLabels": [], + "LeafNodeText": "passes outside a context", + "State": "pending", + "StartTime": "2022-12-14T14:22:04.752611-05:00", + "EndTime": "0001-01-01T00:00:00Z", + "RunTime": 0, + "ParallelProcess": 1, + "NumAttempts": 0, + "MaxFlakeAttempts": 0, + "MaxMustPassRepeatedly": 0 + } + ] + }, + { + "SuitePath": "/Users/kylekthompson/src/captain-examples/go-ginkgo/internal/pkg2", + "SuiteDescription": "Pkg2 Suite", + "SuiteLabels": [], + "SuiteSucceeded": false, + "SuiteHasProgrammaticFocus": false, + "SpecialSuiteFailureReasons": null, + "PreRunStats": { + "TotalSpecs": 0, + "SpecsThatWillRun": 0 + }, + "StartTime": "2022-12-14T14:22:09.424879-05:00", + "EndTime": "2022-12-14T14:22:09.428265-05:00", + "RunTime": 3386208, + "SuiteConfig": { + "RandomSeed": 1671045723, + "RandomizeAllSpecs": false, + "FocusStrings": null, + "SkipStrings": null, + "FocusFiles": null, + "SkipFiles": null, + "LabelFilter": "", + "FailOnPending": true, + "FailFast": false, + "FlakeAttempts": 2, + "DryRun": false, + "PollProgressAfter": 0, + "PollProgressInterval": 0, + "Timeout": 3594427686959, + "OutputInterceptorMode": "", + "SourceRoots": null, + "GracePeriod": 30000000000, + "ParallelProcess": 1, + "ParallelTotal": 1, + "ParallelHost": "" + }, + "SpecReports": [] + } +] diff --git a/internal/test/fixtures/go_test.jsonl b/internal/test/fixtures/go_test.jsonl new file mode 100644 index 0000000..2904634 --- /dev/null +++ b/internal/test/fixtures/go_test.jsonl @@ -0,0 +1,237 @@ +AAAHHHHHHHHH not json +{"Time":"2022-12-13T15:06:09.346716-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestBar"} +{"Time":"2022-12-13T15:06:09.34685-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestBar","Output":"=== RUN TestBar\n"} +{"Time":"2022-12-13T15:06:09.34686-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestBar","Output":"--- PASS: TestBar (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.346874-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestBar","Elapsed":0} +{"Time":"2022-12-13T15:06:09.346884-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestBarFailing"} +{"Time":"2022-12-13T15:06:09.346886-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestBarFailing","Output":"=== RUN TestBarFailing\n"} +{"Time":"2022-12-13T15:06:09.346889-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestBarFailing","Output":" bar_test.go:18: Bar: arg != Bar: not arg\n"} +{"Time":"2022-12-13T15:06:09.346908-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestBarFailing","Output":"--- FAIL: TestBarFailing (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.346915-05:00","Action":"fail","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestBarFailing","Elapsed":0} +{"Time":"2022-12-13T15:06:09.346919-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestSlow"} +{"Time":"2022-12-13T15:06:09.346921-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestSlow","Output":"=== RUN TestSlow\n"} +{"Time":"2022-12-13T15:06:09.398832-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestFoo"} +{"Time":"2022-12-13T15:06:09.398862-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestFoo","Output":"=== RUN TestFoo\n"} +{"Time":"2022-12-13T15:06:09.39889-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestFoo","Output":"--- PASS: TestFoo (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.398892-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestFoo","Elapsed":0} +{"Time":"2022-12-13T15:06:09.398895-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestWhatever"} +{"Time":"2022-12-13T15:06:09.398897-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestWhatever","Output":"=== RUN TestWhatever\n"} +{"Time":"2022-12-13T15:06:09.398899-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestWhatever","Output":"--- PASS: TestWhatever (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.398907-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestWhatever","Elapsed":0} +{"Time":"2022-12-13T15:06:09.398908-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestWhateverFailing"} +{"Time":"2022-12-13T15:06:09.398909-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestWhateverFailing","Output":"=== RUN TestWhateverFailing\n"} +{"Time":"2022-12-13T15:06:09.398912-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestWhateverFailing","Output":" other_test.go:25: Foo: arg != Foo: not arg\n"} +{"Time":"2022-12-13T15:06:09.398921-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestWhateverFailing","Output":"--- FAIL: TestWhateverFailing (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.398924-05:00","Action":"fail","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestWhateverFailing","Elapsed":0} +{"Time":"2022-12-13T15:06:09.398926-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestSkipNoReason"} +{"Time":"2022-12-13T15:06:09.398931-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestSkipNoReason","Output":"=== RUN TestSkipNoReason\n"} +{"Time":"2022-12-13T15:06:09.398932-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestSkipNoReason","Output":" other_test.go:64: \n"} +{"Time":"2022-12-13T15:06:09.398935-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestSkipNoReason","Output":"--- SKIP: TestSkipNoReason (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.398936-05:00","Action":"skip","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestSkipNoReason","Elapsed":0} +{"Time":"2022-12-13T15:06:09.398937-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestSkipReason"} +{"Time":"2022-12-13T15:06:09.398939-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestSkipReason","Output":"=== RUN TestSkipReason\n"} +{"Time":"2022-12-13T15:06:09.39894-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestSkipReason","Output":" other_test.go:68: for a reason\n"} +{"Time":"2022-12-13T15:06:09.398942-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestSkipReason","Output":"--- SKIP: TestSkipReason (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.398944-05:00","Action":"skip","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"TestSkipReason","Elapsed":0} +{"Time":"2022-12-13T15:06:09.398945-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex"} +{"Time":"2022-12-13T15:06:09.398946-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex","Output":"=== RUN FuzzHex\n"} +{"Time":"2022-12-13T15:06:09.398951-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#0"} +{"Time":"2022-12-13T15:06:09.398953-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#0","Output":"=== RUN FuzzHex/seed#0\n"} +{"Time":"2022-12-13T15:06:09.399014-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#1"} +{"Time":"2022-12-13T15:06:09.399019-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#1","Output":"=== RUN FuzzHex/seed#1\n"} +{"Time":"2022-12-13T15:06:09.399024-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#2"} +{"Time":"2022-12-13T15:06:09.399026-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#2","Output":"=== RUN FuzzHex/seed#2\n"} +{"Time":"2022-12-13T15:06:09.399028-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#3"} +{"Time":"2022-12-13T15:06:09.399031-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#3","Output":"=== RUN FuzzHex/seed#3\n"} +{"Time":"2022-12-13T15:06:09.399033-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#4"} +{"Time":"2022-12-13T15:06:09.399036-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#4","Output":"=== RUN FuzzHex/seed#4\n"} +{"Time":"2022-12-13T15:06:09.399046-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#5"} +{"Time":"2022-12-13T15:06:09.399052-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#5","Output":"=== RUN FuzzHex/seed#5\n"} +{"Time":"2022-12-13T15:06:09.399056-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex","Output":"--- PASS: FuzzHex (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.399059-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#0","Output":" --- PASS: FuzzHex/seed#0 (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.399061-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#0","Elapsed":0} +{"Time":"2022-12-13T15:06:09.399063-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#1","Output":" --- PASS: FuzzHex/seed#1 (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.399065-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#1","Elapsed":0} +{"Time":"2022-12-13T15:06:09.399067-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#2","Output":" --- PASS: FuzzHex/seed#2 (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.399068-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#2","Elapsed":0} +{"Time":"2022-12-13T15:06:09.399071-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#3","Output":" --- PASS: FuzzHex/seed#3 (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.399077-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#3","Elapsed":0} +{"Time":"2022-12-13T15:06:09.399081-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#4","Output":" --- PASS: FuzzHex/seed#4 (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.399082-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#4","Elapsed":0} +{"Time":"2022-12-13T15:06:09.399084-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#5","Output":" --- PASS: FuzzHex/seed#5 (0.00s)\n"} +{"Time":"2022-12-13T15:06:09.399086-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex/seed#5","Elapsed":0} +{"Time":"2022-12-13T15:06:09.399087-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg2","Test":"FuzzHex","Elapsed":0} +{"Time":"2022-12-13T15:06:09.399089-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Output":"FAIL\n"} +{"Time":"2022-12-13T15:06:09.399388-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg2","Output":"FAIL\tgithub.com/captain-examples/go-testing/internal/pkg2\t0.119s\n"} +{"Time":"2022-12-13T15:06:09.39941-05:00","Action":"fail","Package":"github.com/captain-examples/go-testing/internal/pkg2","Elapsed":0.119} +{"Time":"2022-12-13T15:06:10.847015-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestSlow","Output":"--- PASS: TestSlow (1.50s)\n"} +{"Time":"2022-12-13T15:06:10.847053-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestSlow","Elapsed":1.5} +{"Time":"2022-12-13T15:06:10.847067-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFoo"} +{"Time":"2022-12-13T15:06:10.847073-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFoo","Output":"=== RUN TestFoo\n"} +{"Time":"2022-12-13T15:06:10.847082-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFoo","Output":"--- PASS: TestFoo (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.847089-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFoo","Elapsed":0} +{"Time":"2022-12-13T15:06:10.847092-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooFailing"} +{"Time":"2022-12-13T15:06:10.847095-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooFailing","Output":"=== RUN TestFooFailing\n"} +{"Time":"2022-12-13T15:06:10.847099-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooFailing","Output":" foo_test.go:18: Foo: arg != Foo: not arg\n"} +{"Time":"2022-12-13T15:06:10.847104-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooFailing","Output":"--- FAIL: TestFooFailing (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.847109-05:00","Action":"fail","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooFailing","Elapsed":0} +{"Time":"2022-12-13T15:06:10.847118-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable"} +{"Time":"2022-12-13T15:06:10.847126-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable","Output":"=== RUN TestFooTable\n"} +{"Time":"2022-12-13T15:06:10.84713-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/0"} +{"Time":"2022-12-13T15:06:10.847133-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/0","Output":"=== RUN TestFooTable/0\n"} +{"Time":"2022-12-13T15:06:10.847136-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/0","Output":" foo_test.go:25: running 1\n"} +{"Time":"2022-12-13T15:06:10.847139-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/0","Output":" foo_test.go:26: running 2\n"} +{"Time":"2022-12-13T15:06:10.847143-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/0","Output":" foo_test.go:27: running 3\n"} +{"Time":"2022-12-13T15:06:10.847146-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/0","Output":" foo_test.go:28: running 4\n"} +{"Time":"2022-12-13T15:06:10.847149-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/0","Output":" foo_test.go:29: running 5\n"} +{"Time":"2022-12-13T15:06:10.847158-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/1"} +{"Time":"2022-12-13T15:06:10.847162-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/1","Output":"=== RUN TestFooTable/1\n"} +{"Time":"2022-12-13T15:06:10.847165-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/1","Output":" foo_test.go:25: running 1\n"} +{"Time":"2022-12-13T15:06:10.847168-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/1","Output":" foo_test.go:26: running 2\n"} +{"Time":"2022-12-13T15:06:10.847171-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/1","Output":" foo_test.go:27: running 3\n"} +{"Time":"2022-12-13T15:06:10.847175-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/1","Output":" foo_test.go:28: running 4\n"} +{"Time":"2022-12-13T15:06:10.847179-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/1","Output":" foo_test.go:29: running 5\n"} +{"Time":"2022-12-13T15:06:10.847182-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/2"} +{"Time":"2022-12-13T15:06:10.847185-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/2","Output":"=== RUN TestFooTable/2\n"} +{"Time":"2022-12-13T15:06:10.847189-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/2","Output":" foo_test.go:25: running 1\n"} +{"Time":"2022-12-13T15:06:10.847192-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/2","Output":" foo_test.go:26: running 2\n"} +{"Time":"2022-12-13T15:06:10.847195-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/2","Output":" foo_test.go:27: running 3\n"} +{"Time":"2022-12-13T15:06:10.847198-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/2","Output":" foo_test.go:28: running 4\n"} +{"Time":"2022-12-13T15:06:10.847203-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/2","Output":" foo_test.go:29: running 5\n"} +{"Time":"2022-12-13T15:06:10.847208-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/3"} +{"Time":"2022-12-13T15:06:10.847227-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/3","Output":"=== RUN TestFooTable/3\n"} +{"Time":"2022-12-13T15:06:10.847232-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/3","Output":" foo_test.go:25: running 1\n"} +{"Time":"2022-12-13T15:06:10.847237-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/3","Output":" foo_test.go:26: running 2\n"} +{"Time":"2022-12-13T15:06:10.847253-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/3","Output":" foo_test.go:27: running 3\n"} +{"Time":"2022-12-13T15:06:10.847257-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/3","Output":" foo_test.go:28: running 4\n"} +{"Time":"2022-12-13T15:06:10.847259-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/3","Output":" foo_test.go:29: running 5\n"} +{"Time":"2022-12-13T15:06:10.847263-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/4"} +{"Time":"2022-12-13T15:06:10.847265-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/4","Output":"=== RUN TestFooTable/4\n"} +{"Time":"2022-12-13T15:06:10.847268-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/4","Output":" foo_test.go:25: running 1\n"} +{"Time":"2022-12-13T15:06:10.847276-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/4","Output":" foo_test.go:26: running 2\n"} +{"Time":"2022-12-13T15:06:10.847279-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/4","Output":" foo_test.go:27: running 3\n"} +{"Time":"2022-12-13T15:06:10.847298-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/4","Output":" foo_test.go:28: running 4\n"} +{"Time":"2022-12-13T15:06:10.847302-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/4","Output":" foo_test.go:29: running 5\n"} +{"Time":"2022-12-13T15:06:10.847306-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable","Output":"--- PASS: TestFooTable (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.847329-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/0","Output":" --- PASS: TestFooTable/0 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.847333-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/0","Elapsed":0} +{"Time":"2022-12-13T15:06:10.847337-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/1","Output":" --- PASS: TestFooTable/1 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.84734-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/1","Elapsed":0} +{"Time":"2022-12-13T15:06:10.847343-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/2","Output":" --- PASS: TestFooTable/2 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.847347-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/2","Elapsed":0} +{"Time":"2022-12-13T15:06:10.84735-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/3","Output":" --- PASS: TestFooTable/3 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.847353-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/3","Elapsed":0} +{"Time":"2022-12-13T15:06:10.847371-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/4","Output":" --- PASS: TestFooTable/4 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.847386-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable/4","Elapsed":0} +{"Time":"2022-12-13T15:06:10.847392-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooTable","Elapsed":0} +{"Time":"2022-12-13T15:06:10.847395-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing"} +{"Time":"2022-12-13T15:06:10.847398-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing","Output":"=== RUN TestTableFailing\n"} +{"Time":"2022-12-13T15:06:10.847405-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/0"} +{"Time":"2022-12-13T15:06:10.847408-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/0","Output":"=== RUN TestTableFailing/0\n"} +{"Time":"2022-12-13T15:06:10.847412-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/0","Output":" foo_test.go:37: failed\n"} +{"Time":"2022-12-13T15:06:10.847415-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/1"} +{"Time":"2022-12-13T15:06:10.847418-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/1","Output":"=== RUN TestTableFailing/1\n"} +{"Time":"2022-12-13T15:06:10.847422-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/1","Output":" foo_test.go:37: failed\n"} +{"Time":"2022-12-13T15:06:10.847426-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/2"} +{"Time":"2022-12-13T15:06:10.847429-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/2","Output":"=== RUN TestTableFailing/2\n"} +{"Time":"2022-12-13T15:06:10.847435-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/2","Output":" foo_test.go:37: failed\n"} +{"Time":"2022-12-13T15:06:10.847439-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/3"} +{"Time":"2022-12-13T15:06:10.847445-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/3","Output":"=== RUN TestTableFailing/3\n"} +{"Time":"2022-12-13T15:06:10.847462-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/3","Output":" foo_test.go:37: failed\n"} +{"Time":"2022-12-13T15:06:10.847466-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/4"} +{"Time":"2022-12-13T15:06:10.847468-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/4","Output":"=== RUN TestTableFailing/4\n"} +{"Time":"2022-12-13T15:06:10.847472-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/4","Output":" foo_test.go:37: failed\n"} +{"Time":"2022-12-13T15:06:10.847484-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing","Output":"--- FAIL: TestTableFailing (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.847489-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/0","Output":" --- FAIL: TestTableFailing/0 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.847493-05:00","Action":"fail","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/0","Elapsed":0} +{"Time":"2022-12-13T15:06:10.847496-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/1","Output":" --- FAIL: TestTableFailing/1 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.847499-05:00","Action":"fail","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/1","Elapsed":0} +{"Time":"2022-12-13T15:06:10.847507-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/2","Output":" --- FAIL: TestTableFailing/2 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.84751-05:00","Action":"fail","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/2","Elapsed":0} +{"Time":"2022-12-13T15:06:10.847513-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/3","Output":" --- FAIL: TestTableFailing/3 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.847517-05:00","Action":"fail","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/3","Elapsed":0} +{"Time":"2022-12-13T15:06:10.847519-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/4","Output":" --- FAIL: TestTableFailing/4 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.847523-05:00","Action":"fail","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing/4","Elapsed":0} +{"Time":"2022-12-13T15:06:10.84753-05:00","Action":"fail","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestTableFailing","Elapsed":0} +{"Time":"2022-12-13T15:06:10.847533-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable"} +{"Time":"2022-12-13T15:06:10.847541-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable","Output":"=== RUN TestFooParallelTable\n"} +{"Time":"2022-12-13T15:06:10.847544-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/0"} +{"Time":"2022-12-13T15:06:10.847547-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/0","Output":"=== RUN TestFooParallelTable/0\n"} +{"Time":"2022-12-13T15:06:10.84755-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/0","Output":"=== PAUSE TestFooParallelTable/0\n"} +{"Time":"2022-12-13T15:06:10.847553-05:00","Action":"pause","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/0"} +{"Time":"2022-12-13T15:06:10.847556-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1"} +{"Time":"2022-12-13T15:06:10.847559-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1","Output":"=== RUN TestFooParallelTable/1\n"} +{"Time":"2022-12-13T15:06:10.84759-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1","Output":"=== PAUSE TestFooParallelTable/1\n"} +{"Time":"2022-12-13T15:06:10.847616-05:00","Action":"pause","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1"} +{"Time":"2022-12-13T15:06:10.847622-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2"} +{"Time":"2022-12-13T15:06:10.847628-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2","Output":"=== RUN TestFooParallelTable/2\n"} +{"Time":"2022-12-13T15:06:10.847634-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2","Output":"=== PAUSE TestFooParallelTable/2\n"} +{"Time":"2022-12-13T15:06:10.847657-05:00","Action":"pause","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2"} +{"Time":"2022-12-13T15:06:10.847669-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3"} +{"Time":"2022-12-13T15:06:10.847674-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3","Output":"=== RUN TestFooParallelTable/3\n"} +{"Time":"2022-12-13T15:06:10.84768-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3","Output":"=== PAUSE TestFooParallelTable/3\n"} +{"Time":"2022-12-13T15:06:10.847683-05:00","Action":"pause","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3"} +{"Time":"2022-12-13T15:06:10.847688-05:00","Action":"run","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/4"} +{"Time":"2022-12-13T15:06:10.847703-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/4","Output":"=== RUN TestFooParallelTable/4\n"} +{"Time":"2022-12-13T15:06:10.847714-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/4","Output":"=== PAUSE TestFooParallelTable/4\n"} +{"Time":"2022-12-13T15:06:10.847717-05:00","Action":"pause","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/4"} +{"Time":"2022-12-13T15:06:10.84772-05:00","Action":"cont","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/0"} +{"Time":"2022-12-13T15:06:10.847746-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/0","Output":"=== CONT TestFooParallelTable/0\n"} +{"Time":"2022-12-13T15:06:10.847758-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/0","Output":" foo_test.go:46: running in parallel 1\n"} +{"Time":"2022-12-13T15:06:10.847791-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/0","Output":" foo_test.go:47: running in parallel 2\n"} +{"Time":"2022-12-13T15:06:10.847807-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/0","Output":" foo_test.go:48: running in parallel 3\n"} +{"Time":"2022-12-13T15:06:10.847837-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/0","Output":" foo_test.go:49: running in parallel 4\n"} +{"Time":"2022-12-13T15:06:10.847841-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/0","Output":" foo_test.go:50: running in parallel 5\n"} +{"Time":"2022-12-13T15:06:10.847846-05:00","Action":"cont","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1"} +{"Time":"2022-12-13T15:06:10.847862-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1","Output":"=== CONT TestFooParallelTable/1\n"} +{"Time":"2022-12-13T15:06:10.847869-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1","Output":" foo_test.go:46: running in parallel 1\n"} +{"Time":"2022-12-13T15:06:10.847873-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1","Output":" foo_test.go:47: running in parallel 2\n"} +{"Time":"2022-12-13T15:06:10.847878-05:00","Action":"cont","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3"} +{"Time":"2022-12-13T15:06:10.847882-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3","Output":"=== CONT TestFooParallelTable/3\n"} +{"Time":"2022-12-13T15:06:10.847885-05:00","Action":"cont","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1"} +{"Time":"2022-12-13T15:06:10.847888-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1","Output":"=== CONT TestFooParallelTable/1\n"} +{"Time":"2022-12-13T15:06:10.847891-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1","Output":" foo_test.go:48: running in parallel 3\n"} +{"Time":"2022-12-13T15:06:10.847895-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1","Output":" foo_test.go:49: running in parallel 4\n"} +{"Time":"2022-12-13T15:06:10.847899-05:00","Action":"cont","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/4"} +{"Time":"2022-12-13T15:06:10.847902-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/4","Output":"=== CONT TestFooParallelTable/4\n"} +{"Time":"2022-12-13T15:06:10.847905-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/4","Output":" foo_test.go:46: running in parallel 1\n"} +{"Time":"2022-12-13T15:06:10.847909-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/4","Output":" foo_test.go:47: running in parallel 2\n"} +{"Time":"2022-12-13T15:06:10.847912-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/4","Output":" foo_test.go:48: running in parallel 3\n"} +{"Time":"2022-12-13T15:06:10.847915-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/4","Output":" foo_test.go:49: running in parallel 4\n"} +{"Time":"2022-12-13T15:06:10.847919-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/4","Output":" foo_test.go:50: running in parallel 5\n"} +{"Time":"2022-12-13T15:06:10.847937-05:00","Action":"cont","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2"} +{"Time":"2022-12-13T15:06:10.84794-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2","Output":"=== CONT TestFooParallelTable/2\n"} +{"Time":"2022-12-13T15:06:10.847945-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2","Output":" foo_test.go:46: running in parallel 1\n"} +{"Time":"2022-12-13T15:06:10.847949-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2","Output":" foo_test.go:47: running in parallel 2\n"} +{"Time":"2022-12-13T15:06:10.847952-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2","Output":" foo_test.go:48: running in parallel 3\n"} +{"Time":"2022-12-13T15:06:10.847956-05:00","Action":"cont","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3"} +{"Time":"2022-12-13T15:06:10.847959-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3","Output":"=== CONT TestFooParallelTable/3\n"} +{"Time":"2022-12-13T15:06:10.847963-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3","Output":" foo_test.go:46: running in parallel 1\n"} +{"Time":"2022-12-13T15:06:10.847966-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3","Output":" foo_test.go:47: running in parallel 2\n"} +{"Time":"2022-12-13T15:06:10.847971-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3","Output":" foo_test.go:48: running in parallel 3\n"} +{"Time":"2022-12-13T15:06:10.847974-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3","Output":" foo_test.go:49: running in parallel 4\n"} +{"Time":"2022-12-13T15:06:10.847977-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3","Output":" foo_test.go:50: running in parallel 5\n"} +{"Time":"2022-12-13T15:06:10.84798-05:00","Action":"cont","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1"} +{"Time":"2022-12-13T15:06:10.847984-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1","Output":"=== CONT TestFooParallelTable/1\n"} +{"Time":"2022-12-13T15:06:10.847987-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1","Output":" foo_test.go:50: running in parallel 5\n"} +{"Time":"2022-12-13T15:06:10.847991-05:00","Action":"cont","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2"} +{"Time":"2022-12-13T15:06:10.847994-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2","Output":"=== CONT TestFooParallelTable/2\n"} +{"Time":"2022-12-13T15:06:10.847999-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2","Output":" foo_test.go:49: running in parallel 4\n"} +{"Time":"2022-12-13T15:06:10.848002-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2","Output":" foo_test.go:50: running in parallel 5\n"} +{"Time":"2022-12-13T15:06:10.848008-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable","Output":"--- PASS: TestFooParallelTable (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.848012-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/0","Output":" --- PASS: TestFooParallelTable/0 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.848016-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/0","Elapsed":0} +{"Time":"2022-12-13T15:06:10.848019-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/4","Output":" --- PASS: TestFooParallelTable/4 (0.00s)\n"} +AAAHHHHHHHHH not json +{"Time":"2022-12-13T15:06:10.848023-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/4","Elapsed":0} +{"Time":"2022-12-13T15:06:10.848027-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3","Output":" --- PASS: TestFooParallelTable/3 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.84803-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/3","Elapsed":0} +{"Time":"2022-12-13T15:06:10.848035-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1","Output":" --- PASS: TestFooParallelTable/1 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.848038-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/1","Elapsed":0} +{"Time":"2022-12-13T15:06:10.848042-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2","Output":" --- PASS: TestFooParallelTable/2 (0.00s)\n"} +{"Time":"2022-12-13T15:06:10.848045-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable/2","Elapsed":0} +{"Time":"2022-12-13T15:06:10.848048-05:00","Action":"pass","Package":"github.com/captain-examples/go-testing/internal/pkg1","Test":"TestFooParallelTable","Elapsed":0} +{"Time":"2022-12-13T15:06:10.848051-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Output":"FAIL\n"} +{"Time":"2022-12-13T15:06:10.848963-05:00","Action":"output","Package":"github.com/captain-examples/go-testing/internal/pkg1","Output":"FAIL\tgithub.com/captain-examples/go-testing/internal/pkg1\t1.584s\n"} +{"Time":"2022-12-13T15:06:10.848972-05:00","Action":"fail","Package":"github.com/captain-examples/go-testing/internal/pkg1","Elapsed":1.584} +AAAHHHHHHHHH not json diff --git a/internal/test/fixtures/go_test_timeout.jsonl b/internal/test/fixtures/go_test_timeout.jsonl new file mode 100644 index 0000000..19f025c --- /dev/null +++ b/internal/test/fixtures/go_test_timeout.jsonl @@ -0,0 +1,11 @@ +{"Time":"2026-02-06T22:02:43.175647483Z","Action":"start","Package":"github.com/rwx-research/mint/agent-go"} +{"Time":"2026-02-06T22:02:43.178232496Z","Action":"run","Package":"github.com/rwx-research/mint/agent-go","Test":"TestLoadConfig_Success"} +{"Time":"2026-02-06T22:02:43.178240496Z","Action":"output","Package":"github.com/rwx-research/mint/agent-go","Test":"TestLoadConfig_Success","Output":"=== RUN TestLoadConfig_Success\n"} +{"Time":"2026-02-06T22:02:43.178264997Z","Action":"output","Package":"github.com/rwx-research/mint/agent-go","Test":"TestLoadConfig_Success","Output":"--- PASS: TestLoadConfig_Success (0.00s)\n"} +{"Time":"2026-02-06T22:02:43.178267039Z","Action":"pass","Package":"github.com/rwx-research/mint/agent-go","Test":"TestLoadConfig_Success","Elapsed":0} +{"Time":"2026-02-06T22:02:43.178570795Z","Action":"run","Package":"github.com/rwx-research/mint/agent-go","Test":"TestThreeSeconds"} +{"Time":"2026-02-06T22:02:43.178571795Z","Action":"output","Package":"github.com/rwx-research/mint/agent-go","Test":"TestThreeSeconds","Output":"=== RUN TestThreeSeconds\n"} +{"Time":"2026-02-06T22:02:44.182898736Z","Action":"output","Package":"github.com/rwx-research/mint/agent-go","Test":"TestThreeSeconds","Output":"panic: test timed out after 1s\n"} +{"Time":"2026-02-06T22:02:44.185338996Z","Action":"output","Package":"github.com/rwx-research/mint/agent-go","Test":"TestThreeSeconds","Output":"exit status 2\n"} +{"Time":"2026-02-06T22:02:44.185400956Z","Action":"output","Package":"github.com/rwx-research/mint/agent-go","Output":"FAIL\tgithub.com/rwx-research/mint/agent-go\t1.009s\n"} +{"Time":"2026-02-06T22:02:44.185415539Z","Action":"fail","Package":"github.com/rwx-research/mint/agent-go","Elapsed":1.01} diff --git a/internal/test/fixtures/integration-tests/captain-configs/junit-xml-reporter.printf-yaml b/internal/test/fixtures/integration-tests/captain-configs/junit-xml-reporter.printf-yaml new file mode 100644 index 0000000..6e2eed5 --- /dev/null +++ b/internal/test/fixtures/integration-tests/captain-configs/junit-xml-reporter.printf-yaml @@ -0,0 +1,29 @@ +cloud: + api-host: "" + disabled: false + insecure: false +flags: {} +output: + debug: false +test-suites: + captain-cli-functional-tests: + command: bash -c 'exit 123' + fail-on-upload-error: true + output: + print-summary: false + reporters: + junit-xml: %s + quiet: false + results: + framework: "" + language: "" + path: fixtures/integration-tests/oss-rspec-failed-not-quarantined.json-UNIQUE-* + retries: + attempts: 0 + command: "" + fail-fast: false + flaky-attempts: 0 + max-tests: "" + post-retry-commands: [] + pre-retry-commands: [] + intermediate-artifacts-path: "" diff --git a/internal/test/fixtures/integration-tests/captain-configs/markdown-summary-reporter.printf-yaml b/internal/test/fixtures/integration-tests/captain-configs/markdown-summary-reporter.printf-yaml new file mode 100644 index 0000000..3ae01b3 --- /dev/null +++ b/internal/test/fixtures/integration-tests/captain-configs/markdown-summary-reporter.printf-yaml @@ -0,0 +1,29 @@ +cloud: + api-host: "" + disabled: false + insecure: false +flags: {} +output: + debug: false +test-suites: + captain-cli-functional-tests: + command: bash -c 'exit 123' + fail-on-upload-error: true + output: + print-summary: false + reporters: + markdown-summary: %s + quiet: false + results: + framework: "" + language: "" + path: fixtures/integration-tests/oss-rspec-failed-not-quarantined.json-UNIQUE-* + retries: + attempts: 0 + command: "" + fail-fast: false + flaky-attempts: 0 + max-tests: "" + post-retry-commands: [] + pre-retry-commands: [] + intermediate-artifacts-path: "" diff --git a/internal/test/fixtures/integration-tests/captain-configs/rwx-v1-json-reporter.printf-yaml b/internal/test/fixtures/integration-tests/captain-configs/rwx-v1-json-reporter.printf-yaml new file mode 100644 index 0000000..8114893 --- /dev/null +++ b/internal/test/fixtures/integration-tests/captain-configs/rwx-v1-json-reporter.printf-yaml @@ -0,0 +1,29 @@ +cloud: + api-host: "" + disabled: false + insecure: false +flags: {} +output: + debug: false +test-suites: + captain-cli-functional-tests: + command: bash -c 'exit 123' + fail-on-upload-error: true + output: + print-summary: true + reporters: + rwx-v1-json: %s + quiet: false + results: + framework: "" + language: "" + path: fixtures/integration-tests/oss-rspec-failed-not-quarantined.json-UNIQUE-* + retries: + attempts: 0 + command: "" + fail-fast: false + flaky-attempts: 0 + max-tests: "" + post-retry-commands: [] + pre-retry-commands: [] + intermediate-artifacts-path: "" diff --git a/internal/test/fixtures/integration-tests/partition-config.yaml b/internal/test/fixtures/integration-tests/partition-config.yaml new file mode 100644 index 0000000..42e67da --- /dev/null +++ b/internal/test/fixtures/integration-tests/partition-config.yaml @@ -0,0 +1,39 @@ +test-suites: + oss-run-with-partition: + command: bash -c 'echo "default run" && exit 123' + fail-on-upload-error: true + results: + language: Ruby + framework: RSpec + path: ./fixtures/integration-tests/rspec-passed.json-UNIQUE-* + partition: + command: echo "test {{ testFiles }}" + globs: + - ./fixtures/integration-tests/partition/*.rb + + oss-run-with-partition-with-multiple-globs: + command: bash -c 'exit 123' + fail-on-upload-error: true + results: + language: Ruby + framework: RSpec + path: ./fixtures/integration-tests/rspec-passed.json-UNIQUE-* + partition: + command: echo "test {{ testFiles }}" + globs: + - ./fixtures/integration-tests/partition/*_spec.rb + - ./fixtures/integration-tests/partition/*.rb + + oss-run-with-partition-with-custom-delimiter: + command: bash -c 'exit 123' + fail-on-upload-error: true + results: + language: Ruby + framework: RSpec + path: ./fixtures/integration-tests/rspec-passed.json-UNIQUE-* + partition: + command: echo "test {{ testFiles }}" + delimiter: " -hi- " + globs: + - ./fixtures/integration-tests/partition/*_spec.rb + - ./fixtures/integration-tests/partition/*.rb diff --git a/internal/test/fixtures/integration-tests/partition/a_spec.rb b/internal/test/fixtures/integration-tests/partition/a_spec.rb new file mode 100644 index 0000000..e69de29 diff --git a/internal/test/fixtures/integration-tests/partition/b_spec.rb b/internal/test/fixtures/integration-tests/partition/b_spec.rb new file mode 100644 index 0000000..e69de29 diff --git a/internal/test/fixtures/integration-tests/partition/c_spec.rb b/internal/test/fixtures/integration-tests/partition/c_spec.rb new file mode 100644 index 0000000..e69de29 diff --git a/internal/test/fixtures/integration-tests/partition/d_spec.rb b/internal/test/fixtures/integration-tests/partition/d_spec.rb new file mode 100644 index 0000000..e69de29 diff --git a/internal/test/fixtures/integration-tests/partition/rspec-partition.json b/internal/test/fixtures/integration-tests/partition/rspec-partition.json new file mode 100644 index 0000000..55fb000 --- /dev/null +++ b/internal/test/fixtures/integration-tests/partition/rspec-partition.json @@ -0,0 +1,53 @@ +{ + "version": "3.12.0", + "examples": [ + { + "id": "./fixtures/integration-tests/partition/a_spec.rb[1:1]", + "description": "A is slowest", + "full_description": "A is slowest", + "status": "passed", + "file_path": "./fixtures/integration-tests/partition/a_spec.rb", + "line_number": 2, + "run_time": 4.0, + "pending_message": null + }, + { + "id": "./fixtures/integration-tests/partition/b_spec.rb[1:1]", + "description": "B is second slowest", + "full_description": "B is second slowest", + "status": "passed", + "file_path": "./fixtures/integration-tests/partition/b_spec.rb", + "line_number": 2, + "run_time": 3.0, + "pending_message": null + }, + { + "id": "./fixtures/integration-tests/partition/c_spec.rb[1:1]", + "description": "C is third slowest", + "full_description": "C is third slowest", + "status": "passed", + "file_path": "./fixtures/integration-tests/partition/c_spec.rb", + "line_number": 2, + "run_time": 2.0, + "pending_message": null + }, + { + "id": "./fixtures/integration-tests/partition/d_spec.rb[1:1]", + "description": "D is slowest of all", + "full_description": "D is slowest of all", + "status": "passed", + "file_path": "./fixtures/integration-tests/partition/d_spec.rb", + "line_number": 2, + "run_time": 1.0, + "pending_message": null + } + ], + "summary": { + "duration": 10.0, + "example_count": 4, + "failure_count": 0, + "pending_count": 0, + "errors_outside_of_examples_count": 0 + }, + "summary_line": "4 examples, 0 failures" +} diff --git a/internal/test/fixtures/integration-tests/partition/x.rb b/internal/test/fixtures/integration-tests/partition/x.rb new file mode 100644 index 0000000..e69de29 diff --git a/internal/test/fixtures/integration-tests/partition/y.rb b/internal/test/fixtures/integration-tests/partition/y.rb new file mode 100644 index 0000000..e69de29 diff --git a/internal/test/fixtures/integration-tests/partition/z.rb b/internal/test/fixtures/integration-tests/partition/z.rb new file mode 100644 index 0000000..e69de29 diff --git a/internal/test/fixtures/integration-tests/retries/buildkite-rspec-failed-not-quarantined.json b/internal/test/fixtures/integration-tests/retries/buildkite-rspec-failed-not-quarantined.json new file mode 100644 index 0000000..b70b7b6 --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/buildkite-rspec-failed-not-quarantined.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is failing","full_description":"is failing","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/retries/buildkite-rspec-quarantine.json b/internal/test/fixtures/integration-tests/retries/buildkite-rspec-quarantine.json new file mode 100644 index 0000000..4aafeca --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/buildkite-rspec-quarantine.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is flaky","full_description":"is flaky","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} \ No newline at end of file diff --git a/internal/test/fixtures/integration-tests/retries/circleci-rspec-failed-not-quarantined.json b/internal/test/fixtures/integration-tests/retries/circleci-rspec-failed-not-quarantined.json new file mode 100644 index 0000000..b70b7b6 --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/circleci-rspec-failed-not-quarantined.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is failing","full_description":"is failing","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/retries/circleci-rspec-quarantine.json b/internal/test/fixtures/integration-tests/retries/circleci-rspec-quarantine.json new file mode 100644 index 0000000..4aafeca --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/circleci-rspec-quarantine.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is flaky","full_description":"is flaky","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} \ No newline at end of file diff --git a/internal/test/fixtures/integration-tests/retries/generic-rspec-failed-not-quarantined.json b/internal/test/fixtures/integration-tests/retries/generic-rspec-failed-not-quarantined.json new file mode 100644 index 0000000..b70b7b6 --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/generic-rspec-failed-not-quarantined.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is failing","full_description":"is failing","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/retries/generic-rspec-quarantine.json b/internal/test/fixtures/integration-tests/retries/generic-rspec-quarantine.json new file mode 100644 index 0000000..4aafeca --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/generic-rspec-quarantine.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is flaky","full_description":"is flaky","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} \ No newline at end of file diff --git a/internal/test/fixtures/integration-tests/retries/github-actions-rspec-failed-not-quarantined.json b/internal/test/fixtures/integration-tests/retries/github-actions-rspec-failed-not-quarantined.json new file mode 100644 index 0000000..b70b7b6 --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/github-actions-rspec-failed-not-quarantined.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is failing","full_description":"is failing","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/retries/github-actions-rspec-quarantine.json b/internal/test/fixtures/integration-tests/retries/github-actions-rspec-quarantine.json new file mode 100644 index 0000000..4aafeca --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/github-actions-rspec-quarantine.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is flaky","full_description":"is flaky","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} \ No newline at end of file diff --git a/internal/test/fixtures/integration-tests/retries/gitlab-ci-rspec-failed-not-quarantined.json b/internal/test/fixtures/integration-tests/retries/gitlab-ci-rspec-failed-not-quarantined.json new file mode 100644 index 0000000..b70b7b6 --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/gitlab-ci-rspec-failed-not-quarantined.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is failing","full_description":"is failing","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/retries/gitlab-ci-rspec-quarantine.json b/internal/test/fixtures/integration-tests/retries/gitlab-ci-rspec-quarantine.json new file mode 100644 index 0000000..4aafeca --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/gitlab-ci-rspec-quarantine.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is flaky","full_description":"is flaky","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} \ No newline at end of file diff --git a/internal/test/fixtures/integration-tests/retries/oss-rspec-failed-not-quarantined.json b/internal/test/fixtures/integration-tests/retries/oss-rspec-failed-not-quarantined.json new file mode 100644 index 0000000..b70b7b6 --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/oss-rspec-failed-not-quarantined.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is failing","full_description":"is failing","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/retries/oss-rspec-quarantine.json b/internal/test/fixtures/integration-tests/retries/oss-rspec-quarantine.json new file mode 100644 index 0000000..4aafeca --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/oss-rspec-quarantine.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is flaky","full_description":"is flaky","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} \ No newline at end of file diff --git a/internal/test/fixtures/integration-tests/retries/with-config-buildkite-rspec-failed-not-quarantined.json b/internal/test/fixtures/integration-tests/retries/with-config-buildkite-rspec-failed-not-quarantined.json new file mode 100644 index 0000000..b70b7b6 --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/with-config-buildkite-rspec-failed-not-quarantined.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is failing","full_description":"is failing","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/retries/with-config-circleci-rspec-failed-not-quarantined.json b/internal/test/fixtures/integration-tests/retries/with-config-circleci-rspec-failed-not-quarantined.json new file mode 100644 index 0000000..b70b7b6 --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/with-config-circleci-rspec-failed-not-quarantined.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is failing","full_description":"is failing","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/retries/with-config-generic-rspec-failed-not-quarantined.json b/internal/test/fixtures/integration-tests/retries/with-config-generic-rspec-failed-not-quarantined.json new file mode 100644 index 0000000..b70b7b6 --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/with-config-generic-rspec-failed-not-quarantined.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is failing","full_description":"is failing","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/retries/with-config-github-actions-rspec-failed-not-quarantined.json b/internal/test/fixtures/integration-tests/retries/with-config-github-actions-rspec-failed-not-quarantined.json new file mode 100644 index 0000000..b70b7b6 --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/with-config-github-actions-rspec-failed-not-quarantined.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is failing","full_description":"is failing","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/retries/with-config-gitlab-ci-rspec-failed-not-quarantined.json b/internal/test/fixtures/integration-tests/retries/with-config-gitlab-ci-rspec-failed-not-quarantined.json new file mode 100644 index 0000000..b70b7b6 --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/with-config-gitlab-ci-rspec-failed-not-quarantined.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is failing","full_description":"is failing","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/retries/with-config-oss-rspec-failed-not-quarantined.json b/internal/test/fixtures/integration-tests/retries/with-config-oss-rspec-failed-not-quarantined.json new file mode 100644 index 0000000..b70b7b6 --- /dev/null +++ b/internal/test/fixtures/integration-tests/retries/with-config-oss-rspec-failed-not-quarantined.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is failing","full_description":"is failing","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/retry-config.yaml b/internal/test/fixtures/integration-tests/retry-config.yaml new file mode 100644 index 0000000..e9877ad --- /dev/null +++ b/internal/test/fixtures/integration-tests/retry-config.yaml @@ -0,0 +1,71 @@ +test-suites: + buildkite-quarantined-retries-with-config: + command: bash -c 'exit 123' + fail-on-upload-error: true + results: + language: Ruby + framework: RSpec + path: ./fixtures/integration-tests/with-config-buildkite-rspec-failed-not-quarantined.json-UNIQUE-* + retries: + attempts: 2 + command: echo "{{ tests }}" + circleci-quarantined-retries-with-config: + command: bash -c 'exit 123' + fail-on-upload-error: true + results: + language: Ruby + framework: RSpec + path: ./fixtures/integration-tests/with-config-circleci-rspec-failed-not-quarantined.json-UNIQUE-* + retries: + attempts: 2 + command: echo "{{ tests }}" + github-actions-quarantined-retries-with-config: + command: bash -c 'exit 123' + fail-on-upload-error: true + results: + language: Ruby + framework: RSpec + path: ./fixtures/integration-tests/with-config-github-actions-rspec-failed-not-quarantined.json-UNIQUE-* + retries: + attempts: 2 + command: echo "{{ tests }}" + gitlab-ci-quarantined-retries-with-config: + command: bash -c 'exit 123' + fail-on-upload-error: true + results: + language: Ruby + framework: RSpec + path: ./fixtures/integration-tests/with-config-gitlab-ci-rspec-failed-not-quarantined.json-UNIQUE-* + retries: + attempts: 2 + command: echo "{{ tests }}" + generic-quarantined-retries-with-config: + command: bash -c 'exit 123' + fail-on-upload-error: true + results: + language: Ruby + framework: RSpec + path: ./fixtures/integration-tests/with-config-generic-rspec-failed-not-quarantined.json-UNIQUE-* + retries: + attempts: 2 + command: echo "{{ tests }}" + inherited-env-quarantined-retries-with-config: + command: bash -c 'exit 123' + fail-on-upload-error: true + results: + language: Ruby + framework: RSpec + path: ./fixtures/integration-tests/with-config-inherited-env-rspec-failed-not-quarantined.json-UNIQUE-* + retries: + attempts: 2 + command: echo "{{ tests }}" + oss-quarantined-retries-with-config: + command: bash -c 'exit 123' + fail-on-upload-error: true + results: + language: Ruby + framework: RSpec + path: ./fixtures/integration-tests/with-config-oss-rspec-failed-not-quarantined.json-UNIQUE-* + retries: + attempts: 2 + command: echo "{{ tests }}" diff --git a/internal/test/fixtures/integration-tests/rspec-failed-not-quarantined.json b/internal/test/fixtures/integration-tests/rspec-failed-not-quarantined.json new file mode 100644 index 0000000..b70b7b6 --- /dev/null +++ b/internal/test/fixtures/integration-tests/rspec-failed-not-quarantined.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is failing","full_description":"is failing","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/rspec-passed-not-quarantined.json b/internal/test/fixtures/integration-tests/rspec-passed-not-quarantined.json new file mode 100644 index 0000000..4d1e31c --- /dev/null +++ b/internal/test/fixtures/integration-tests/rspec-passed-not-quarantined.json @@ -0,0 +1,24 @@ +{ + "version": "3.12.0", + "examples": [ + { + "id": "./x.rb[1:1]", + "description": "is failing", + "full_description": "is failing", + "status": "passed", + "file_path": "./x.rb", + "line_number": 2, + "run_time": 0.00038, + "pending_message": null + } + ], + "summary": { + "duration": 0.000769, + "example_count": 1, + "failure_count": 0, + "pending_count": 0, + "errors_outside_of_examples_count": 0 + }, + "summary_line": "1 example, 0 failures" +} + diff --git a/internal/test/fixtures/integration-tests/rspec-passed.json b/internal/test/fixtures/integration-tests/rspec-passed.json new file mode 100644 index 0000000..3a43864 --- /dev/null +++ b/internal/test/fixtures/integration-tests/rspec-passed.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is flaky","full_description":"is flaky","status":"passed","file_path":"./x.rb","line_number":2,"run_time":0.00038,"pending_message":null}],"summary":{"duration":0.000769,"example_count":1,"failure_count":0,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 0 failures"} \ No newline at end of file diff --git a/internal/test/fixtures/integration-tests/rspec-quarantine.json b/internal/test/fixtures/integration-tests/rspec-quarantine.json new file mode 100644 index 0000000..4aafeca --- /dev/null +++ b/internal/test/fixtures/integration-tests/rspec-quarantine.json @@ -0,0 +1 @@ +{"version":"3.12.0","examples":[{"id":"./x.rb[1:1]","description":"is flaky","full_description":"is flaky","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"1 example, 1 failure"} \ No newline at end of file diff --git a/internal/test/fixtures/integration-tests/rspec-quarantined-with-other-errors.json b/internal/test/fixtures/integration-tests/rspec-quarantined-with-other-errors.json new file mode 100644 index 0000000..6070be8 --- /dev/null +++ b/internal/test/fixtures/integration-tests/rspec-quarantined-with-other-errors.json @@ -0,0 +1 @@ +{"version":"3.12.0","messages":["there was an other error"],"examples":[{"id":"./x.rb[1:1]","description":"is flaky","full_description":"is flaky","status":"failed","file_path":"./x.rb","line_number":2,"run_time":0.008077,"pending_message":null,"exception":{"class":"RSpec::Expectations::ExpectationNotMetError","message":"expected: >= 2\n got: 1","backtrace":["/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:102:in `block in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-support-3.12.0/lib/rspec/support.rb:111:in `notify_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/fail_with.rb:35:in `fail_with'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:40:in `handle_failure'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:65:in `to'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-expectations-3.12.0/lib/rspec/expectations/expectation_target.rb:101:in `to'","/Users/dan/code/captain-cli/x.rb:3:in `block (2 levels) in '","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `instance_exec'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:263:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `block in run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/hooks.rb:486:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example.rb:259:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:642:in `run_examples'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/example_group.rb:607:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `map'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/configuration.rb:2070:in `with_suite_hooks'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:116:in `block in run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/reporter.rb:74:in `report'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:115:in `run_specs'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:89:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:71:in `run'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/lib/rspec/core/runner.rb:45:in `invoke'","/Users/dan/.local/share/gem/ruby/3.1.0/gems/rspec-core-3.12.0/exe/rspec:4:in `'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `load'","/Users/dan/.rbenv/versions/3.1.2/bin/rspec:25:in `
    '"]}}],"summary":{"duration":0.009075,"example_count":1,"failure_count":1,"pending_count":0,"errors_outside_of_examples_count":1},"summary_line":"1 example, 1 failure"} diff --git a/internal/test/fixtures/integration-tests/xunit-failed-not-quarantined.xml b/internal/test/fixtures/integration-tests/xunit-failed-not-quarantined.xml new file mode 100644 index 0000000..4a267e9 --- /dev/null +++ b/internal/test/fixtures/integration-tests/xunit-failed-not-quarantined.xml @@ -0,0 +1,21 @@ + + + + + + some/path/to/ExampleTests.cs + 15 + + + + + + + + + + + + diff --git a/internal/test/fixtures/jest.json b/internal/test/fixtures/jest.json new file mode 100644 index 0000000..e6763d7 --- /dev/null +++ b/internal/test/fixtures/jest.json @@ -0,0 +1,238 @@ +{ + "numFailedTestSuites": 3, + "numFailedTests": 6, + "numPassedTestSuites": 0, + "numPassedTests": 6, + "numPendingTestSuites": 0, + "numPendingTests": 3, + "numRuntimeErrorTestSuites": 0, + "numTodoTests": 3, + "numTotalTestSuites": 3, + "numTotalTests": 18, + "openHandles": [], + "snapshot": { + "added": 0, + "didUpdate": false, + "failure": false, + "filesAdded": 0, + "filesRemoved": 0, + "filesRemovedList": [], + "filesUnmatched": 0, + "filesUpdated": 0, + "matched": 0, + "total": 0, + "unchecked": 0, + "uncheckedKeysByFile": [], + "unmatched": 0, + "updated": 0 + }, + "startTime": 1665517160011, + "success": false, + "testResults": [ + { + "assertionResults": [ + { + "ancestorTitles": [], + "duration": null, + "failureMessages": [], + "fullName": "is top-level pending", + "location": null, + "status": "todo", + "title": "is top-level pending" + }, + { + "ancestorTitles": [], + "duration": null, + "failureMessages": [], + "fullName": "is top-level skipped", + "location": null, + "status": "pending", + "title": "is top-level skipped" + }, + { + "ancestorTitles": [], + "duration": 1, + "failureMessages": [], + "fullName": "is top-level passing", + "location": null, + "status": "passed", + "title": "is top-level passing" + }, + { + "ancestorTitles": [], + "duration": 2, + "failureMessages": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js:12:16)\n at Promise.then.completed (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:333:28)\n at new Promise ()\n at callAsyncCircusFn (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:259:10)\n at _callCircusTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:277:40)\n at processTicksAndRejections (node:internal/process/task_queues:96:5)\n at _runTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:209:3)\n at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:97:9)\n at run (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:31:3)\n at runAndTransformResultsToJestFormat (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:136:21)\n at jestAdapter (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:92:19)\n at runTestInternal (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:407:16)\n at runTest (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:491:34)\n at Object.worker (/home/runner/work/captain/captain/node_modules/jest-runner/build/testWorker.js:133:12)" + ], + "fullName": "is top-level failing", + "location": null, + "status": "failed", + "title": "is top-level failing" + }, + { + "ancestorTitles": [], + "duration": 653, + "failureMessages": [], + "fullName": "is asynchronously top-level passing", + "location": null, + "status": "passed", + "title": "is asynchronously top-level passing" + }, + { + "ancestorTitles": [], + "duration": 651, + "failureMessages": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js:22:16)" + ], + "fullName": "is asynchronously top-level failing", + "location": null, + "status": "failed", + "title": "is asynchronously top-level failing" + } + ], + "endTime": 1665517162106, + "message": "\u001b[1m\u001b[31m \u001b[1m● \u001b[22m\u001b[1mis top-level failing\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\n Expected: \u001b[32mfalse\u001b[39m\n Received: \u001b[31mtrue\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 10 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 11 |\u001b[39m it(\u001b[32m\"is top-level failing\"\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[2m\u001b[39m\u001b[90m 12 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[2m\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 13 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 14 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 15 |\u001b[39m it(\u001b[32m\"is asynchronously top-level passing\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[2mat Object.toBe (\u001b[22m\u001b[2m\u001b[0m\u001b[36mapp/javascript/controllers/top_level.test.js\u001b[39m\u001b[0m\u001b[2m:12:16)\u001b[22m\u001b[2m\u001b[22m\n\n\u001b[1m\u001b[31m \u001b[1m● \u001b[22m\u001b[1mis asynchronously top-level failing\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\n Expected: \u001b[32mfalse\u001b[39m\n Received: \u001b[31mtrue\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 20 |\u001b[39m it(\u001b[32m\"is asynchronously top-level failing\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 21 |\u001b[39m \u001b[36mawait\u001b[39m \u001b[36mnew\u001b[39m \u001b[33mPromise\u001b[39m(resolve \u001b[33m=>\u001b[39m setTimeout(resolve\u001b[33m,\u001b[39m \u001b[35m650\u001b[39m))\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[2m\u001b[39m\u001b[90m 22 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[2m\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 23 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 24 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[2mat Object.toBe (\u001b[22m\u001b[2m\u001b[0m\u001b[36mapp/javascript/controllers/top_level.test.js\u001b[39m\u001b[0m\u001b[2m:22:16)\u001b[22m\u001b[2m\u001b[22m\n", + "name": "/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js", + "startTime": 1665517160298, + "status": "failed", + "summary": "" + }, + { + "assertionResults": [ + { + "ancestorTitles": ["even", "more nesting"], + "duration": null, + "failureMessages": [], + "fullName": "even more nesting is pending", + "location": null, + "status": "todo", + "title": "is pending" + }, + { + "ancestorTitles": ["even", "more nesting"], + "duration": null, + "failureMessages": [], + "fullName": "even more nesting is skipped", + "location": null, + "status": "pending", + "title": "is skipped" + }, + { + "ancestorTitles": ["even", "more nesting"], + "duration": 1, + "failureMessages": [], + "fullName": "even more nesting is passing", + "location": null, + "status": "passed", + "title": "is passing" + }, + { + "ancestorTitles": ["even", "more nesting"], + "duration": 1, + "failureMessages": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js:14:20)\n at Promise.then.completed (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:333:28)\n at new Promise ()\n at callAsyncCircusFn (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:259:10)\n at _callCircusTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:277:40)\n at processTicksAndRejections (node:internal/process/task_queues:96:5)\n at _runTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:209:3)\n at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:97:9)\n at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:91:9)\n at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:91:9)\n at run (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:31:3)\n at runAndTransformResultsToJestFormat (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:136:21)\n at jestAdapter (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:92:19)\n at runTestInternal (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:407:16)\n at runTest (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:491:34)\n at Object.worker (/home/runner/work/captain/captain/node_modules/jest-runner/build/testWorker.js:133:12)" + ], + "fullName": "even more nesting is failing", + "location": null, + "status": "failed", + "title": "is failing" + }, + { + "ancestorTitles": ["even", "more nesting"], + "duration": 654, + "failureMessages": [], + "fullName": "even more nesting is asynchronously passing", + "location": null, + "status": "passed", + "title": "is asynchronously passing" + }, + { + "ancestorTitles": ["even", "more nesting"], + "duration": 651, + "failureMessages": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js:24:20)" + ], + "fullName": "even more nesting is asynchronously failing", + "location": null, + "status": "failed", + "title": "is asynchronously failing" + } + ], + "endTime": 1665517162134, + "message": "\u001b[1m\u001b[31m \u001b[1m● \u001b[22m\u001b[1meven › more nesting › is failing\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\n Expected: \u001b[32mfalse\u001b[39m\n Received: \u001b[31mtrue\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 12 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 13 |\u001b[39m it(\u001b[32m\"is failing\"\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[2m\u001b[39m\u001b[90m 14 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[2m\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 15 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 16 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 17 |\u001b[39m it(\u001b[32m\"is asynchronously passing\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[2mat Object.toBe (\u001b[22m\u001b[2m\u001b[0m\u001b[36mapp/javascript/controllers/nested/more/nested.test.js\u001b[39m\u001b[0m\u001b[2m:14:20)\u001b[22m\u001b[2m\u001b[22m\n\n\u001b[1m\u001b[31m \u001b[1m● \u001b[22m\u001b[1meven › more nesting › is asynchronously failing\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\n Expected: \u001b[32mfalse\u001b[39m\n Received: \u001b[31mtrue\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 22 |\u001b[39m it(\u001b[32m\"is asynchronously failing\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 23 |\u001b[39m \u001b[36mawait\u001b[39m \u001b[36mnew\u001b[39m \u001b[33mPromise\u001b[39m(resolve \u001b[33m=>\u001b[39m setTimeout(resolve\u001b[33m,\u001b[39m \u001b[35m650\u001b[39m))\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[2m\u001b[39m\u001b[90m 24 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[2m\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 25 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 26 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 27 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[2mat Object.toBe (\u001b[22m\u001b[2m\u001b[0m\u001b[36mapp/javascript/controllers/nested/more/nested.test.js\u001b[39m\u001b[0m\u001b[2m:24:20)\u001b[22m\u001b[2m\u001b[22m\n", + "name": "/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js", + "startTime": 1665517160298, + "status": "failed", + "summary": "" + }, + { + "assertionResults": [ + { + "ancestorTitles": ["one level of nesting"], + "duration": null, + "failureMessages": [], + "fullName": "one level of nesting is pending", + "location": null, + "status": "todo", + "title": "is pending" + }, + { + "ancestorTitles": ["one level of nesting"], + "duration": null, + "failureMessages": [], + "fullName": "one level of nesting is skipped", + "location": null, + "status": "pending", + "title": "is skipped" + }, + { + "ancestorTitles": ["one level of nesting"], + "duration": 0, + "failureMessages": [], + "fullName": "one level of nesting is passing", + "location": null, + "status": "passed", + "title": "is passing" + }, + { + "ancestorTitles": ["one level of nesting"], + "duration": 1, + "failureMessages": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js:13:18)\n at Promise.then.completed (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:333:28)\n at new Promise ()\n at callAsyncCircusFn (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:259:10)\n at _callCircusTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:277:40)\n at processTicksAndRejections (node:internal/process/task_queues:96:5)\n at _runTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:209:3)\n at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:97:9)\n at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:91:9)\n at run (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:31:3)\n at runAndTransformResultsToJestFormat (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:136:21)\n at jestAdapter (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:92:19)\n at runTestInternal (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:407:16)\n at runTest (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:491:34)\n at Object.worker (/home/runner/work/captain/captain/node_modules/jest-runner/build/testWorker.js:133:12)" + ], + "fullName": "one level of nesting is failing", + "location": null, + "status": "failed", + "title": "is failing" + }, + { + "ancestorTitles": ["one level of nesting"], + "duration": 653, + "failureMessages": [], + "fullName": "one level of nesting is asynchronously passing", + "location": null, + "status": "passed", + "title": "is asynchronously passing" + }, + { + "ancestorTitles": ["one level of nesting"], + "duration": 652, + "failureMessages": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js:23:18)" + ], + "fullName": "one level of nesting is asynchronously failing", + "location": null, + "status": "failed", + "title": "is asynchronously failing" + } + ], + "endTime": 1665517162135, + "message": "\u001b[1m\u001b[31m \u001b[1m● \u001b[22m\u001b[1mone level of nesting › is failing\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\n Expected: \u001b[32mfalse\u001b[39m\n Received: \u001b[31mtrue\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 11 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 12 |\u001b[39m it(\u001b[32m\"is failing\"\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[2m\u001b[39m\u001b[90m 13 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[2m\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 14 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 15 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 16 |\u001b[39m it(\u001b[32m\"is asynchronously passing\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[2mat Object.toBe (\u001b[22m\u001b[2m\u001b[0m\u001b[36mapp/javascript/controllers/nested/nested.test.js\u001b[39m\u001b[0m\u001b[2m:13:18)\u001b[22m\u001b[2m\u001b[22m\n\n\u001b[1m\u001b[31m \u001b[1m● \u001b[22m\u001b[1mone level of nesting › is asynchronously failing\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\n Expected: \u001b[32mfalse\u001b[39m\n Received: \u001b[31mtrue\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 21 |\u001b[39m it(\u001b[32m\"is asynchronously failing\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 22 |\u001b[39m \u001b[36mawait\u001b[39m \u001b[36mnew\u001b[39m \u001b[33mPromise\u001b[39m(resolve \u001b[33m=>\u001b[39m setTimeout(resolve\u001b[33m,\u001b[39m \u001b[35m650\u001b[39m))\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[2m\u001b[39m\u001b[90m 23 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[2m\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 24 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 25 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 26 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[2mat Object.toBe (\u001b[22m\u001b[2m\u001b[0m\u001b[36mapp/javascript/controllers/nested/nested.test.js\u001b[39m\u001b[0m\u001b[2m:23:18)\u001b[22m\u001b[2m\u001b[22m\n", + "name": "/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js", + "startTime": 1665517160299, + "status": "failed", + "summary": "" + } + ], + "wasInterrupted": false +} diff --git a/internal/test/fixtures/jest_with_duplicates.json b/internal/test/fixtures/jest_with_duplicates.json new file mode 100644 index 0000000..4ff4172 --- /dev/null +++ b/internal/test/fixtures/jest_with_duplicates.json @@ -0,0 +1,258 @@ +{ + "numFailedTestSuites": 3, + "numFailedTests": 6, + "numPassedTestSuites": 0, + "numPassedTests": 6, + "numPendingTestSuites": 0, + "numPendingTests": 3, + "numRuntimeErrorTestSuites": 0, + "numTodoTests": 3, + "numTotalTestSuites": 3, + "numTotalTests": 18, + "openHandles": [], + "snapshot": { + "added": 0, + "didUpdate": false, + "failure": false, + "filesAdded": 0, + "filesRemoved": 0, + "filesRemovedList": [], + "filesUnmatched": 0, + "filesUpdated": 0, + "matched": 0, + "total": 0, + "unchecked": 0, + "uncheckedKeysByFile": [], + "unmatched": 0, + "updated": 0 + }, + "startTime": 1665517160011, + "success": false, + "testResults": [ + { + "assertionResults": [ + { + "ancestorTitles": [], + "duration": null, + "failureMessages": [], + "fullName": "is top-level pending", + "location": null, + "status": "todo", + "title": "is top-level pending" + }, + { + "ancestorTitles": [], + "duration": null, + "failureMessages": [], + "fullName": "is top-level skipped", + "location": null, + "status": "pending", + "title": "is top-level skipped" + }, + { + "ancestorTitles": [], + "duration": 1, + "failureMessages": [], + "fullName": "is top-level passing", + "location": null, + "status": "passed", + "title": "is top-level passing" + }, + { + "ancestorTitles": [], + "duration": 1, + "failureMessages": [], + "fullName": "is top-level passing", + "location": null, + "status": "passed", + "title": "is top-level passing" + }, + { + "ancestorTitles": [], + "duration": 2, + "failureMessages": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js:12:16)\n at Promise.then.completed (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:333:28)\n at new Promise ()\n at callAsyncCircusFn (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:259:10)\n at _callCircusTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:277:40)\n at processTicksAndRejections (node:internal/process/task_queues:96:5)\n at _runTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:209:3)\n at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:97:9)\n at run (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:31:3)\n at runAndTransformResultsToJestFormat (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:136:21)\n at jestAdapter (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:92:19)\n at runTestInternal (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:407:16)\n at runTest (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:491:34)\n at Object.worker (/home/runner/work/captain/captain/node_modules/jest-runner/build/testWorker.js:133:12)" + ], + "fullName": "is top-level failing", + "location": null, + "status": "failed", + "title": "is top-level failing" + }, + { + "ancestorTitles": [], + "duration": 653, + "failureMessages": [], + "fullName": "is asynchronously top-level passing", + "location": null, + "status": "passed", + "title": "is asynchronously top-level passing" + }, + { + "ancestorTitles": [], + "duration": 651, + "failureMessages": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js:22:16)" + ], + "fullName": "is asynchronously top-level failing", + "location": null, + "status": "failed", + "title": "is asynchronously top-level failing" + }, + { + "ancestorTitles": [], + "duration": 651, + "failureMessages": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js:22:16)" + ], + "fullName": "is asynchronously top-level failing", + "location": null, + "status": "failed", + "title": "is asynchronously top-level failing" + } + ], + "endTime": 1665517162106, + "message": "\u001b[1m\u001b[31m \u001b[1m● \u001b[22m\u001b[1mis top-level failing\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\n Expected: \u001b[32mfalse\u001b[39m\n Received: \u001b[31mtrue\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 10 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 11 |\u001b[39m it(\u001b[32m\"is top-level failing\"\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[2m\u001b[39m\u001b[90m 12 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[2m\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 13 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 14 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 15 |\u001b[39m it(\u001b[32m\"is asynchronously top-level passing\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[2mat Object.toBe (\u001b[22m\u001b[2m\u001b[0m\u001b[36mapp/javascript/controllers/top_level.test.js\u001b[39m\u001b[0m\u001b[2m:12:16)\u001b[22m\u001b[2m\u001b[22m\n\n\u001b[1m\u001b[31m \u001b[1m● \u001b[22m\u001b[1mis asynchronously top-level failing\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\n Expected: \u001b[32mfalse\u001b[39m\n Received: \u001b[31mtrue\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 20 |\u001b[39m it(\u001b[32m\"is asynchronously top-level failing\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 21 |\u001b[39m \u001b[36mawait\u001b[39m \u001b[36mnew\u001b[39m \u001b[33mPromise\u001b[39m(resolve \u001b[33m=>\u001b[39m setTimeout(resolve\u001b[33m,\u001b[39m \u001b[35m650\u001b[39m))\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[2m\u001b[39m\u001b[90m 22 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[2m\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 23 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 24 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[2mat Object.toBe (\u001b[22m\u001b[2m\u001b[0m\u001b[36mapp/javascript/controllers/top_level.test.js\u001b[39m\u001b[0m\u001b[2m:22:16)\u001b[22m\u001b[2m\u001b[22m\n", + "name": "/home/runner/work/captain/captain/app/javascript/controllers/top_level.test.js", + "startTime": 1665517160298, + "status": "failed", + "summary": "" + }, + { + "assertionResults": [ + { + "ancestorTitles": ["even", "more nesting"], + "duration": null, + "failureMessages": [], + "fullName": "even more nesting is pending", + "location": null, + "status": "todo", + "title": "is pending" + }, + { + "ancestorTitles": ["even", "more nesting"], + "duration": null, + "failureMessages": [], + "fullName": "even more nesting is skipped", + "location": null, + "status": "pending", + "title": "is skipped" + }, + { + "ancestorTitles": ["even", "more nesting"], + "duration": 1, + "failureMessages": [], + "fullName": "even more nesting is passing", + "location": null, + "status": "passed", + "title": "is passing" + }, + { + "ancestorTitles": ["even", "more nesting"], + "duration": 1, + "failureMessages": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js:14:20)\n at Promise.then.completed (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:333:28)\n at new Promise ()\n at callAsyncCircusFn (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:259:10)\n at _callCircusTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:277:40)\n at processTicksAndRejections (node:internal/process/task_queues:96:5)\n at _runTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:209:3)\n at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:97:9)\n at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:91:9)\n at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:91:9)\n at run (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:31:3)\n at runAndTransformResultsToJestFormat (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:136:21)\n at jestAdapter (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:92:19)\n at runTestInternal (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:407:16)\n at runTest (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:491:34)\n at Object.worker (/home/runner/work/captain/captain/node_modules/jest-runner/build/testWorker.js:133:12)" + ], + "fullName": "even more nesting is failing", + "location": null, + "status": "failed", + "title": "is failing" + }, + { + "ancestorTitles": ["even", "more nesting"], + "duration": 654, + "failureMessages": [], + "fullName": "even more nesting is asynchronously passing", + "location": null, + "status": "passed", + "title": "is asynchronously passing" + }, + { + "ancestorTitles": ["even", "more nesting"], + "duration": 651, + "failureMessages": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js:24:20)" + ], + "fullName": "even more nesting is asynchronously failing", + "location": null, + "status": "failed", + "title": "is asynchronously failing" + } + ], + "endTime": 1665517162134, + "message": "\u001b[1m\u001b[31m \u001b[1m● \u001b[22m\u001b[1meven › more nesting › is failing\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\n Expected: \u001b[32mfalse\u001b[39m\n Received: \u001b[31mtrue\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 12 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 13 |\u001b[39m it(\u001b[32m\"is failing\"\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[2m\u001b[39m\u001b[90m 14 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[2m\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 15 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 16 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 17 |\u001b[39m it(\u001b[32m\"is asynchronously passing\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[2mat Object.toBe (\u001b[22m\u001b[2m\u001b[0m\u001b[36mapp/javascript/controllers/nested/more/nested.test.js\u001b[39m\u001b[0m\u001b[2m:14:20)\u001b[22m\u001b[2m\u001b[22m\n\n\u001b[1m\u001b[31m \u001b[1m● \u001b[22m\u001b[1meven › more nesting › is asynchronously failing\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\n Expected: \u001b[32mfalse\u001b[39m\n Received: \u001b[31mtrue\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 22 |\u001b[39m it(\u001b[32m\"is asynchronously failing\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 23 |\u001b[39m \u001b[36mawait\u001b[39m \u001b[36mnew\u001b[39m \u001b[33mPromise\u001b[39m(resolve \u001b[33m=>\u001b[39m setTimeout(resolve\u001b[33m,\u001b[39m \u001b[35m650\u001b[39m))\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[2m\u001b[39m\u001b[90m 24 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[2m\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 25 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 26 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 27 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[2mat Object.toBe (\u001b[22m\u001b[2m\u001b[0m\u001b[36mapp/javascript/controllers/nested/more/nested.test.js\u001b[39m\u001b[0m\u001b[2m:24:20)\u001b[22m\u001b[2m\u001b[22m\n", + "name": "/home/runner/work/captain/captain/app/javascript/controllers/nested/more/nested.test.js", + "startTime": 1665517160298, + "status": "failed", + "summary": "" + }, + { + "assertionResults": [ + { + "ancestorTitles": ["one level of nesting"], + "duration": null, + "failureMessages": [], + "fullName": "one level of nesting is pending", + "location": null, + "status": "todo", + "title": "is pending" + }, + { + "ancestorTitles": ["one level of nesting"], + "duration": null, + "failureMessages": [], + "fullName": "one level of nesting is skipped", + "location": null, + "status": "pending", + "title": "is skipped" + }, + { + "ancestorTitles": ["one level of nesting"], + "duration": 0, + "failureMessages": [], + "fullName": "one level of nesting is passing", + "location": null, + "status": "passed", + "title": "is passing" + }, + { + "ancestorTitles": ["one level of nesting"], + "duration": 1, + "failureMessages": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js:13:18)\n at Promise.then.completed (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:333:28)\n at new Promise ()\n at callAsyncCircusFn (/home/runner/work/captain/captain/node_modules/jest-circus/build/utils.js:259:10)\n at _callCircusTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:277:40)\n at processTicksAndRejections (node:internal/process/task_queues:96:5)\n at _runTest (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:209:3)\n at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:97:9)\n at _runTestsForDescribeBlock (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:91:9)\n at run (/home/runner/work/captain/captain/node_modules/jest-circus/build/run.js:31:3)\n at runAndTransformResultsToJestFormat (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:136:21)\n at jestAdapter (/home/runner/work/captain/captain/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:92:19)\n at runTestInternal (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:407:16)\n at runTest (/home/runner/work/captain/captain/node_modules/jest-runner/build/runTest.js:491:34)\n at Object.worker (/home/runner/work/captain/captain/node_modules/jest-runner/build/testWorker.js:133:12)" + ], + "fullName": "one level of nesting is failing", + "location": null, + "status": "failed", + "title": "is failing" + }, + { + "ancestorTitles": ["one level of nesting"], + "duration": 653, + "failureMessages": [], + "fullName": "one level of nesting is asynchronously passing", + "location": null, + "status": "passed", + "title": "is asynchronously passing" + }, + { + "ancestorTitles": ["one level of nesting"], + "duration": 652, + "failureMessages": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at Object.toBe (/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js:23:18)" + ], + "fullName": "one level of nesting is asynchronously failing", + "location": null, + "status": "failed", + "title": "is asynchronously failing" + } + ], + "endTime": 1665517162135, + "message": "\u001b[1m\u001b[31m \u001b[1m● \u001b[22m\u001b[1mone level of nesting › is failing\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\n Expected: \u001b[32mfalse\u001b[39m\n Received: \u001b[31mtrue\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 11 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 12 |\u001b[39m it(\u001b[32m\"is failing\"\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[2m\u001b[39m\u001b[90m 13 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[2m\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 14 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 15 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 16 |\u001b[39m it(\u001b[32m\"is asynchronously passing\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[2mat Object.toBe (\u001b[22m\u001b[2m\u001b[0m\u001b[36mapp/javascript/controllers/nested/nested.test.js\u001b[39m\u001b[0m\u001b[2m:13:18)\u001b[22m\u001b[2m\u001b[22m\n\n\u001b[1m\u001b[31m \u001b[1m● \u001b[22m\u001b[1mone level of nesting › is asynchronously failing\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\n Expected: \u001b[32mfalse\u001b[39m\n Received: \u001b[31mtrue\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 21 |\u001b[39m it(\u001b[32m\"is asynchronously failing\"\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 22 |\u001b[39m \u001b[36mawait\u001b[39m \u001b[36mnew\u001b[39m \u001b[33mPromise\u001b[39m(resolve \u001b[33m=>\u001b[39m setTimeout(resolve\u001b[33m,\u001b[39m \u001b[35m650\u001b[39m))\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[2m\u001b[39m\u001b[90m 23 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[2m\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 24 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 25 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 26 |\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[2mat Object.toBe (\u001b[22m\u001b[2m\u001b[0m\u001b[36mapp/javascript/controllers/nested/nested.test.js\u001b[39m\u001b[0m\u001b[2m:23:18)\u001b[22m\u001b[2m\u001b[22m\n", + "name": "/home/runner/work/captain/captain/app/javascript/controllers/nested/nested.test.js", + "startTime": 1665517160299, + "status": "failed", + "summary": "" + } + ], + "wasInterrupted": false +} diff --git a/internal/test/fixtures/junit-no-testsuites-element.xml b/internal/test/fixtures/junit-no-testsuites-element.xml new file mode 100644 index 0000000..7fb8191 --- /dev/null +++ b/internal/test/fixtures/junit-no-testsuites-element.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/test/fixtures/junit.xml b/internal/test/fixtures/junit.xml new file mode 100644 index 0000000..cec8eee --- /dev/null +++ b/internal/test/fixtures/junit.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/test/fixtures/karma.json b/internal/test/fixtures/karma.json new file mode 100644 index 0000000..fa39d80 --- /dev/null +++ b/internal/test/fixtures/karma.json @@ -0,0 +1,119 @@ +{ + "browsers": { + "87843490": { + "id": "87843490", + "fullName": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36", + "name": "Chrome 109.0.0.0 (Mac OS 10.15.7)", + "state": "DISCONNECTED", + "lastResult": { + "startTime": 1677015045628, + "total": 4, + "success": 2, + "failed": 1, + "skipped": 1, + "totalTime": 6, + "netTime": 2, + "error": true, + "disconnected": false + }, + "disconnectsCount": 0, + "noActivityTimeout": 30000, + "disconnectDelay": 2000 + } + }, + "result": { + "87843490": [ + { + "fullName": "Some test context is a contextual passing test", + "description": "is a contextual passing test", + "id": "spec3", + "log": [], + "skipped": false, + "disabled": false, + "pending": false, + "success": true, + "suite": ["Some test", "context"], + "time": 1, + "executedExpectationsCount": 1, + "passedExpectations": [ + { + "matcherName": "toBe", + "message": "Passed.", + "stack": "", + "passed": true + } + ], + "properties": null + }, + { + "fullName": "Some test is a passing test", + "description": "is a passing test", + "id": "spec0", + "log": [], + "skipped": false, + "disabled": false, + "pending": false, + "success": true, + "suite": ["Some test"], + "time": 1, + "executedExpectationsCount": 2, + "passedExpectations": [ + { + "matcherName": "toBe", + "message": "Passed.", + "stack": "", + "passed": true + }, + { + "matcherName": "toBe", + "message": "Passed.", + "stack": "", + "passed": true + } + ], + "properties": null + }, + { + "fullName": "Some test is a skipped test", + "description": "is a skipped test", + "id": "spec1", + "log": [], + "skipped": true, + "disabled": false, + "pending": true, + "success": true, + "suite": ["Some test"], + "time": 0, + "executedExpectationsCount": 0, + "passedExpectations": [], + "properties": null + }, + { + "fullName": "Some test is a failing test", + "description": "is a failing test", + "id": "spec2", + "log": [ + "Expected true to be false.\n at \n at UserContext. (http://localhost:9876/base/tests/some-test.js?0928787523d1ecf8fe8e5c6b04ef9abb48348e11:19:18)\n at " + ], + "skipped": false, + "disabled": false, + "pending": false, + "success": false, + "suite": ["Some test"], + "time": 0, + "executedExpectationsCount": 1, + "passedExpectations": [], + "properties": null, + "debug_url": "http://localhost:9876/debug.html?spec=Some%20test%20is%20a%20failing%20test" + } + ] + }, + "summary": { + "success": 2, + "failed": 1, + "skipped": 1, + "error": true, + "disconnected": false, + "exitCode": 1 + } +} diff --git a/internal/test/fixtures/merge/rspec-one.json b/internal/test/fixtures/merge/rspec-one.json new file mode 100644 index 0000000..f0479a4 --- /dev/null +++ b/internal/test/fixtures/merge/rspec-one.json @@ -0,0 +1,1484 @@ +{ + "version": "3.11.0", + "examples": [ + { + "id": "./spec/examples/class_spec.rb[1:8:7:3]", + "description": "has top-level aggregated failing tests", + "full_description": "Tests::Case within a context behaves like shared examples has top-level aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 10, + "run_time": 0.002654, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:4]", + "description": "has top-level skipped tests", + "full_description": "Tests::Case within a context behaves like shared examples has top-level skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 15, + "run_time": 3.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:5]", + "description": "has top-level passing pended tests", + "full_description": "Tests::Case within a context behaves like shared examples has top-level passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 20, + "run_time": 0.003018, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:20"] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:6]", + "description": "has top-level failing pended tests", + "full_description": "Tests::Case within a context behaves like shared examples has top-level failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 25, + "run_time": 0.002221, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:1]", + "description": "has passing tests", + "full_description": "Tests::Case within a context behaves like shared examples within a context has passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 31, + "run_time": 0.002212, + "pending_message": null + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:2]", + "description": "has failing tests", + "full_description": "Tests::Case within a context behaves like shared examples within a context has failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 35, + "run_time": 0.002861, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:3]", + "description": "has aggregated failing tests", + "full_description": "Tests::Case within a context behaves like shared examples within a context has aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 39, + "run_time": 0.00304, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:4]", + "description": "has skipped tests", + "full_description": "Tests::Case within a context behaves like shared examples within a context has skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 44, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:5]", + "description": "has passing pended tests", + "full_description": "Tests::Case within a context behaves like shared examples within a context has passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 49, + "run_time": 0.002672, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:49"] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:6]", + "description": "has failing pended tests", + "full_description": "Tests::Case within a context behaves like shared examples within a context has failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 54, + "run_time": 0.002959, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/string_spec.rb[1:1]", + "description": "has top-level passing tests", + "full_description": "some string has top-level passing tests", + "status": "passed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 4, + "run_time": 0.00211, + "pending_message": null + }, + { + "id": "./spec/examples/string_spec.rb[1:2]", + "description": "has top-level failing tests", + "full_description": "some string has top-level failing tests", + "status": "failed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 8, + "run_time": 0.00224, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/string_spec.rb:9:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:3]", + "description": "has top-level aggregated failing tests", + "full_description": "some string has top-level aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 12, + "run_time": 0.002886, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:13:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:14:in `block (2 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:4]", + "description": "has top-level skipped tests", + "full_description": "some string has top-level skipped tests", + "status": "pending", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 17, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/string_spec.rb[1:5]", + "description": "has top-level passing pended tests", + "full_description": "some string has top-level passing pended tests", + "status": "failed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 22, + "run_time": 0.002922, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/string_spec.rb:22"] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:6]", + "description": "has top-level failing pended tests", + "full_description": "some string has top-level failing pended tests", + "status": "pending", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 27, + "run_time": 0.002704, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/string_spec.rb[1:7:1]", + "description": "has top-level passing tests", + "full_description": "some string behaves like shared examples has top-level passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 2, + "run_time": 0.0027, + "pending_message": null + }, + { + "id": "./spec/examples/string_spec.rb[1:7:2]", + "description": "has top-level failing tests", + "full_description": "some string behaves like shared examples has top-level failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 6, + "run_time": 0.002541, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:3]", + "description": "has top-level aggregated failing tests", + "full_description": "some string behaves like shared examples has top-level aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 10, + "run_time": 0.002879, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:4]", + "description": "has top-level skipped tests", + "full_description": "some string behaves like shared examples has top-level skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 15, + "run_time": 3.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/string_spec.rb[1:7:5]", + "description": "has top-level passing pended tests", + "full_description": "some string behaves like shared examples has top-level passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 20, + "run_time": 0.002721, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:20"] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:6]", + "description": "has top-level failing pended tests", + "full_description": "some string behaves like shared examples has top-level failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 25, + "run_time": 0.003249, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:1]", + "description": "has passing tests", + "full_description": "some string behaves like shared examples within a context has passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 31, + "run_time": 0.002793, + "pending_message": null + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:2]", + "description": "has failing tests", + "full_description": "some string behaves like shared examples within a context has failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 35, + "run_time": 0.002262, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:3]", + "description": "has aggregated failing tests", + "full_description": "some string behaves like shared examples within a context has aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 39, + "run_time": 0.002288, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:4]", + "description": "has skipped tests", + "full_description": "some string behaves like shared examples within a context has skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 44, + "run_time": 3.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:5]", + "description": "has passing pended tests", + "full_description": "some string behaves like shared examples within a context has passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 49, + "run_time": 0.002546, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:49"] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:6]", + "description": "has failing pended tests", + "full_description": "some string behaves like shared examples within a context has failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 54, + "run_time": 0.002156, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/string_spec.rb[1:8:1]", + "description": "has passing tests", + "full_description": "some string within a context has passing tests", + "status": "passed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 35, + "run_time": 0.002272, + "pending_message": null + }, + { + "id": "./spec/examples/string_spec.rb[1:8:2]", + "description": "has failing tests", + "full_description": "some string within a context has failing tests", + "status": "failed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 39, + "run_time": 0.002598, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/string_spec.rb:40:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:3]", + "description": "has aggregated failing tests", + "full_description": "some string within a context has aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 43, + "run_time": 0.002331, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:44:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:45:in `block (3 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:4]", + "description": "has skipped tests", + "full_description": "some string within a context has skipped tests", + "status": "pending", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 48, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/string_spec.rb[1:8:5]", + "description": "has passing pended tests", + "full_description": "some string within a context has passing pended tests", + "status": "failed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 53, + "run_time": 0.002118, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/string_spec.rb:53"] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:6]", + "description": "has failing pended tests", + "full_description": "some string within a context has failing pended tests", + "status": "pending", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 58, + "run_time": 0.002429, + "pending_message": "for a reason" + }, + { + "description": "has top-level passing tests", + "full_description": "some string within a context behaves like shared examples has top-level passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 2, + "run_time": 0.00196, + "pending_message": null + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:2]", + "description": "has top-level failing tests", + "full_description": "some string within a context behaves like shared examples has top-level failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 6, + "run_time": 0.001989, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:3]", + "description": "has top-level aggregated failing tests", + "full_description": "some string within a context behaves like shared examples has top-level aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 10, + "run_time": 0.002512, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:4]", + "description": "has top-level skipped tests", + "full_description": "some string within a context behaves like shared examples has top-level skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 15, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:5]", + "description": "has top-level passing pended tests", + "full_description": "some string within a context behaves like shared examples has top-level passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 20, + "run_time": 0.002402, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:20"] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:6]", + "description": "has top-level failing pended tests", + "full_description": "some string within a context behaves like shared examples has top-level failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 25, + "run_time": 0.002197, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:1]", + "description": "has passing tests", + "full_description": "some string within a context behaves like shared examples within a context has passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 31, + "run_time": 0.00217, + "pending_message": null + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:2]", + "description": "has failing tests", + "full_description": "some string within a context behaves like shared examples within a context has failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 35, + "run_time": 0.002212, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:3]", + "description": "has aggregated failing tests", + "full_description": "some string within a context behaves like shared examples within a context has aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 39, + "run_time": 0.003165, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:4]", + "description": "has skipped tests", + "full_description": "some string within a context behaves like shared examples within a context has skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 44, + "run_time": 3.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:5]", + "description": "has passing pended tests", + "full_description": "some string within a context behaves like shared examples within a context has passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 49, + "run_time": 0.002212, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:49"] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:6]", + "description": "has failing pended tests", + "full_description": "some string within a context behaves like shared examples within a context has failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 54, + "run_time": 0.002257, + "pending_message": "for a reason" + } + ], + "summary": { + "duration": 0.1963, + "example_count": 45, + "failure_count": 23, + "pending_count": 16, + "errors_outside_of_examples_count": 0 + }, + "summary_line": "45 examples, 23 failures, 16 pending" +} diff --git a/internal/test/fixtures/merge/rspec-two.json b/internal/test/fixtures/merge/rspec-two.json new file mode 100644 index 0000000..0f88e8d --- /dev/null +++ b/internal/test/fixtures/merge/rspec-two.json @@ -0,0 +1,869 @@ +{ + "version": "3.11.0", + "examples": [ + { + "id": "./spec/examples/class_spec.rb[1:1]", + "description": "has top-level passing tests", + "full_description": "Tests::Case has top-level passing tests", + "status": "passed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 5, + "run_time": 0.030795, + "pending_message": null + }, + { + "id": "./spec/examples/class_spec.rb[1:2]", + "description": "has top-level failing tests", + "full_description": "Tests::Case has top-level failing tests", + "status": "failed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 9, + "run_time": 0.006114, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/class_spec.rb:10:in `block (2 levels) in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:3]", + "description": "has top-level aggregated failing tests", + "full_description": "Tests::Case has top-level aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 13, + "run_time": 0.003131, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:14:in `block (2 levels) in \u003ctop (required)\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:15:in `block (2 levels) in \u003ctop (required)\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:4]", + "description": "has top-level skipped tests", + "full_description": "Tests::Case has top-level skipped tests", + "status": "pending", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 18, + "run_time": 6.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/class_spec.rb[1:5]", + "description": "has top-level passing pended tests", + "full_description": "Tests::Case has top-level passing pended tests", + "status": "failed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 23, + "run_time": 0.003167, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/class_spec.rb:23"] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:6]", + "description": "has top-level failing pended tests", + "full_description": "Tests::Case has top-level failing pended tests", + "status": "pending", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 28, + "run_time": 0.002972, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/class_spec.rb[1:7:1]", + "description": "has top-level passing tests", + "full_description": "Tests::Case behaves like shared examples has top-level passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 2, + "run_time": 0.002982, + "pending_message": null + }, + { + "id": "./spec/examples/class_spec.rb[1:7:2]", + "description": "has top-level failing tests", + "full_description": "Tests::Case behaves like shared examples has top-level failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 6, + "run_time": 0.002971, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:3]", + "description": "has top-level aggregated failing tests", + "full_description": "Tests::Case behaves like shared examples has top-level aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 10, + "run_time": 0.00254, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:4]", + "description": "has top-level skipped tests", + "full_description": "Tests::Case behaves like shared examples has top-level skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 15, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/class_spec.rb[1:7:5]", + "description": "has top-level passing pended tests", + "full_description": "Tests::Case behaves like shared examples has top-level passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 20, + "run_time": 0.003049, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:20"] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:6]", + "description": "has top-level failing pended tests", + "full_description": "Tests::Case behaves like shared examples has top-level failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 25, + "run_time": 0.003055, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:1]", + "description": "has passing tests", + "full_description": "Tests::Case behaves like shared examples within a context has passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 31, + "run_time": 0.002859, + "pending_message": null + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:2]", + "description": "has failing tests", + "full_description": "Tests::Case behaves like shared examples within a context has failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 35, + "run_time": 0.002979, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:3]", + "description": "has aggregated failing tests", + "full_description": "Tests::Case behaves like shared examples within a context has aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 39, + "run_time": 0.002438, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:4]", + "description": "has skipped tests", + "full_description": "Tests::Case behaves like shared examples within a context has skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 44, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:5]", + "description": "has passing pended tests", + "full_description": "Tests::Case behaves like shared examples within a context has passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 49, + "run_time": 0.002583, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:49"] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:6]", + "description": "has failing pended tests", + "full_description": "Tests::Case behaves like shared examples within a context has failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 54, + "run_time": 0.003151, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/class_spec.rb[1:8:1]", + "description": "has passing tests", + "full_description": "Tests::Case within a context has passing tests", + "status": "passed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 36, + "run_time": 0.002502, + "pending_message": null + }, + { + "id": "./spec/examples/class_spec.rb[1:8:2]", + "description": "has failing tests", + "full_description": "Tests::Case within a context has failing tests", + "status": "failed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 40, + "run_time": 0.00306, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/class_spec.rb:41:in `block (3 levels) in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:3]", + "description": "has aggregated failing tests", + "full_description": "Tests::Case within a context has aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 44, + "run_time": 0.003055, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:45:in `block (3 levels) in \u003ctop (required)\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:46:in `block (3 levels) in \u003ctop (required)\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:4]", + "description": "has skipped tests", + "full_description": "Tests::Case within a context has skipped tests", + "status": "pending", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 49, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/class_spec.rb[1:8:5]", + "description": "has passing pended tests", + "full_description": "Tests::Case within a context has passing pended tests", + "status": "failed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 54, + "run_time": 0.002014, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/class_spec.rb:54"] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:6]", + "description": "has failing pended tests", + "full_description": "Tests::Case within a context has failing pended tests", + "status": "pending", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 59, + "run_time": 0.002398, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:1]", + "description": "has top-level passing tests", + "full_description": "Tests::Case within a context behaves like shared examples has top-level passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 2, + "run_time": 0.002994, + "pending_message": null + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:2]", + "description": "has top-level failing tests", + "full_description": "Tests::Case within a context behaves like shared examples has top-level failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 6, + "run_time": 0.002811, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + } + ], + "summary": { + "duration": 0.1963, + "example_count": 26, + "failure_count": 13, + "pending_count": 8, + "errors_outside_of_examples_count": 0 + }, + "summary_line": "26 examples, 13 failures, 8 pending" +} diff --git a/internal/test/fixtures/minitest.xml b/internal/test/fixtures/minitest.xml new file mode 100644 index 0000000..a266b95 --- /dev/null +++ b/internal/test/fixtures/minitest.xml @@ -0,0 +1,56 @@ + + + + + +Failure: +test_fails(Minitest::Result) [/Users/kylekthompson/src/captain-examples/minitest/test/failing_test.rb:5]: +Expected false to be truthy. + + + + +Failure: +test_raises(Minitest::Result) [/Users/kylekthompson/src/captain-examples/minitest/test/failing_test.rb:9]: +RuntimeError: uh oh + /Users/kylekthompson/src/captain-examples/minitest/test/failing_test.rb:9:in `test_raises' + + + + +Failure: +test_raises_custom(Minitest::Result) [/Users/kylekthompson/src/captain-examples/minitest/test/failing_test.rb:13]: +RWX::Example::MyError: uh oh + /Users/kylekthompson/src/captain-examples/minitest/test/failing_test.rb:13:in `test_raises_custom' + + + + +Failure: +test_assert_equal_fails(Minitest::Result) [/Users/kylekthompson/src/captain-examples/minitest/test/rwx/example_test.rb:26]: +Expected: 2 + Actual: 1 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/test/fixtures/mocha.json b/internal/test/fixtures/mocha.json new file mode 100644 index 0000000..01ee500 --- /dev/null +++ b/internal/test/fixtures/mocha.json @@ -0,0 +1,232 @@ +{ + "stats": { + "suites": 2, + "tests": 10, + "passes": 6, + "pending": 1, + "failures": 3, + "start": "2022-12-06T20:48:23.924Z", + "end": "2022-12-06T20:48:24.505Z", + "duration": 581 + }, + "tests": [ + { + "title": "is an async test", + "fullTitle": "is an async test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/async.js", + "duration": 570, + "currentRetry": 0, + "speed": "slow", + "err": {} + }, + { + "title": "is a passing retried test", + "fullTitle": "is a passing retried test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/retries.js", + "duration": 0, + "currentRetry": 2, + "speed": "fast", + "err": {} + }, + { + "title": "is a failing retried test", + "fullTitle": "is a failing retried test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/retries.js", + "duration": 0, + "currentRetry": 10, + "err": { + "stack": "AssertionError [ERR_ASSERTION]: true == false\n at Context. (test/retries.js:12:10)\n at processImmediate (node:internal/timers:466:21)", + "message": "true == false", + "generatedMessage": true, + "name": "AssertionError", + "code": "ERR_ASSERTION", + "actual": "true", + "expected": "false", + "operator": "==" + } + }, + { + "title": "is a top-level sync passing test", + "fullTitle": "is a top-level sync passing test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + }, + { + "title": "is a top-level sync failing test", + "fullTitle": "is a top-level sync failing test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "duration": 0, + "currentRetry": 0, + "err": { + "stack": "AssertionError [ERR_ASSERTION]: true == false\n at Context. (test/top-level-sync.js:8:10)\n at processImmediate (node:internal/timers:466:21)", + "message": "true == false", + "generatedMessage": true, + "name": "AssertionError", + "code": "ERR_ASSERTION", + "actual": "true", + "expected": "false", + "operator": "==" + } + }, + { + "title": "is a top-level sync exceptional test", + "fullTitle": "is a top-level sync exceptional test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "duration": 0, + "currentRetry": 0, + "err": { + "stack": "Error: Uh oh\n at Context. (test/top-level-sync.js:12:9)\n at processImmediate (node:internal/timers:466:21)", + "message": "Uh oh" + } + }, + { + "title": "is a top-level pended test", + "fullTitle": "is a top-level pended test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "currentRetry": 0, + "err": {} + }, + { + "title": "is nested one level", + "fullTitle": "nesting is nested one level", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + }, + { + "title": "is nested one level", + "fullTitle": "nesting more is nested one level", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + }, + { + "title": "is nested two levels", + "fullTitle": "nesting more is nested two levels", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + } + ], + "pending": [ + { + "title": "is a top-level pended test", + "fullTitle": "is a top-level pended test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "currentRetry": 0, + "err": {} + } + ], + "failures": [ + { + "title": "is a failing retried test", + "fullTitle": "is a failing retried test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/retries.js", + "duration": 0, + "currentRetry": 10, + "err": { + "stack": "AssertionError [ERR_ASSERTION]: true == false\n at Context. (test/retries.js:12:10)\n at processImmediate (node:internal/timers:466:21)", + "message": "true == false", + "generatedMessage": true, + "name": "AssertionError", + "code": "ERR_ASSERTION", + "actual": "true", + "expected": "false", + "operator": "==" + } + }, + { + "title": "is a top-level sync failing test", + "fullTitle": "is a top-level sync failing test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "duration": 0, + "currentRetry": 0, + "err": { + "stack": "AssertionError [ERR_ASSERTION]: true == false\n at Context. (test/top-level-sync.js:8:10)\n at processImmediate (node:internal/timers:466:21)", + "message": "true == false", + "generatedMessage": true, + "name": "AssertionError", + "code": "ERR_ASSERTION", + "actual": "true", + "expected": "false", + "operator": "==" + } + }, + { + "title": "is a top-level sync exceptional test", + "fullTitle": "is a top-level sync exceptional test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "duration": 0, + "currentRetry": 0, + "err": { + "stack": "Error: Uh oh\n at Context. (test/top-level-sync.js:12:9)\n at processImmediate (node:internal/timers:466:21)", + "message": "Uh oh" + } + } + ], + "passes": [ + { + "title": "is an async test", + "fullTitle": "is an async test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/async.js", + "duration": 570, + "currentRetry": 0, + "speed": "slow", + "err": {} + }, + { + "title": "is a passing retried test", + "fullTitle": "is a passing retried test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/retries.js", + "duration": 0, + "currentRetry": 2, + "speed": "fast", + "err": {} + }, + { + "title": "is a top-level sync passing test", + "fullTitle": "is a top-level sync passing test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + }, + { + "title": "is nested one level", + "fullTitle": "nesting is nested one level", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + }, + { + "title": "is nested one level", + "fullTitle": "nesting more is nested one level", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + }, + { + "title": "is nested two levels", + "fullTitle": "nesting more is nested two levels", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + } + ] +} diff --git a/internal/test/fixtures/mocha_with_duplicates.json b/internal/test/fixtures/mocha_with_duplicates.json new file mode 100644 index 0000000..e8077bc --- /dev/null +++ b/internal/test/fixtures/mocha_with_duplicates.json @@ -0,0 +1,232 @@ +{ + "stats": { + "suites": 2, + "tests": 10, + "passes": 6, + "pending": 1, + "failures": 3, + "start": "2022-12-06T20:48:23.924Z", + "end": "2022-12-06T20:48:24.505Z", + "duration": 581 + }, + "tests": [ + { + "title": "is an async test with title one", + "fullTitle": "is an async test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/async.js", + "duration": 570, + "currentRetry": 0, + "speed": "slow", + "err": {} + }, + { + "title": "is a passing retried test", + "fullTitle": "is a passing retried test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/retries.js", + "duration": 0, + "currentRetry": 2, + "speed": "fast", + "err": {} + }, + { + "title": "is an async test with title two", + "fullTitle": "is an async test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/async.js", + "duration": 0, + "currentRetry": 10, + "err": { + "stack": "AssertionError [ERR_ASSERTION]: true == false\n at Context. (test/retries.js:12:10)\n at processImmediate (node:internal/timers:466:21)", + "message": "true == false", + "generatedMessage": true, + "name": "AssertionError", + "code": "ERR_ASSERTION", + "actual": "true", + "expected": "false", + "operator": "==" + } + }, + { + "title": "is a top-level sync passing test", + "fullTitle": "is a top-level sync passing test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + }, + { + "title": "is a top-level sync failing test", + "fullTitle": "is a top-level sync failing test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "duration": 0, + "currentRetry": 0, + "err": { + "stack": "AssertionError [ERR_ASSERTION]: true == false\n at Context. (test/top-level-sync.js:8:10)\n at processImmediate (node:internal/timers:466:21)", + "message": "true == false", + "generatedMessage": true, + "name": "AssertionError", + "code": "ERR_ASSERTION", + "actual": "true", + "expected": "false", + "operator": "==" + } + }, + { + "title": "is a top-level sync exceptional test", + "fullTitle": "is a top-level sync exceptional test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "duration": 0, + "currentRetry": 0, + "err": { + "stack": "Error: Uh oh\n at Context. (test/top-level-sync.js:12:9)\n at processImmediate (node:internal/timers:466:21)", + "message": "Uh oh" + } + }, + { + "title": "is a top-level pended test", + "fullTitle": "is a top-level pended test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "currentRetry": 0, + "err": {} + }, + { + "title": "is nested one level", + "fullTitle": "nesting is nested one level", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + }, + { + "title": "is nested one level", + "fullTitle": "nesting more is nested one level", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + }, + { + "title": "is nested two levels", + "fullTitle": "nesting more is nested two levels", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + } + ], + "pending": [ + { + "title": "is a top-level pended test", + "fullTitle": "is a top-level pended test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "currentRetry": 0, + "err": {} + } + ], + "failures": [ + { + "title": "is an async test with title two", + "fullTitle": "is an async test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/async.js", + "duration": 0, + "currentRetry": 10, + "err": { + "stack": "AssertionError [ERR_ASSERTION]: true == false\n at Context. (test/retries.js:12:10)\n at processImmediate (node:internal/timers:466:21)", + "message": "true == false", + "generatedMessage": true, + "name": "AssertionError", + "code": "ERR_ASSERTION", + "actual": "true", + "expected": "false", + "operator": "==" + } + }, + { + "title": "is a top-level sync failing test", + "fullTitle": "is a top-level sync failing test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "duration": 0, + "currentRetry": 0, + "err": { + "stack": "AssertionError [ERR_ASSERTION]: true == false\n at Context. (test/top-level-sync.js:8:10)\n at processImmediate (node:internal/timers:466:21)", + "message": "true == false", + "generatedMessage": true, + "name": "AssertionError", + "code": "ERR_ASSERTION", + "actual": "true", + "expected": "false", + "operator": "==" + } + }, + { + "title": "is a top-level sync exceptional test", + "fullTitle": "is a top-level sync exceptional test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "duration": 0, + "currentRetry": 0, + "err": { + "stack": "Error: Uh oh\n at Context. (test/top-level-sync.js:12:9)\n at processImmediate (node:internal/timers:466:21)", + "message": "Uh oh" + } + } + ], + "passes": [ + { + "title": "is an async test", + "fullTitle": "is an async test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/async.js", + "duration": 570, + "currentRetry": 0, + "speed": "slow", + "err": {} + }, + { + "title": "is a passing retried test", + "fullTitle": "is a passing retried test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/retries.js", + "duration": 0, + "currentRetry": 2, + "speed": "fast", + "err": {} + }, + { + "title": "is a top-level sync passing test", + "fullTitle": "is a top-level sync passing test", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/top-level-sync.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + }, + { + "title": "is nested one level", + "fullTitle": "nesting is nested one level", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + }, + { + "title": "is nested one level", + "fullTitle": "nesting more is nested one level", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + }, + { + "title": "is nested two levels", + "fullTitle": "nesting more is nested two levels", + "file": "/Users/kylekthompson/src/captain-examples/mocha/test/nested/nesting.js", + "duration": 0, + "currentRetry": 0, + "speed": "fast", + "err": {} + } + ] +} diff --git a/internal/test/fixtures/phpunit.xml b/internal/test/fixtures/phpunit.xml new file mode 100644 index 0000000..ed0ae7c --- /dev/null +++ b/internal/test/fixtures/phpunit.xml @@ -0,0 +1,67 @@ + + + + + + + + Tests\Unit\ExampleTest::test_that_true_is_false +Failed asserting that true is false. + +/var/www/html/tests/Unit/ExampleTest.php:23 + + + + + + + + + + + + + + + Tests\Unit\ExampleTest::test_that_is_an_exception +Exception: an exception was thrown + +/var/www/html/tests/Unit/ExampleTest.php:52 + + + Tests\Unit\ExampleTest::test_that_is_a_custom_exception +Tests\Unit\CustomException: an exception was thrown + +/var/www/html/tests/Unit/ExampleTest.php:57 + + + + + + Tests\Unit\Nested\ExampleTest::test_that_true_is_false +Failed asserting that true is false. + +/var/www/html/tests/Unit/Nested/ExampleTest.php:21 + + + Tests\Unit\Nested\ExampleTest::test_that_is_slow +Failed asserting that true is false. + +/var/www/html/tests/Unit/Nested/ExampleTest.php:27 + + + Tests\Unit\Nested\ExampleTest::test_with_no_assertion +This test did not perform any assertions + +/var/www/html/tests/Unit/Nested/ExampleTest.php:30 + + + + + + + + + + + diff --git a/internal/test/fixtures/playwright.json b/internal/test/fixtures/playwright.json new file mode 100644 index 0000000..9c38b33 --- /dev/null +++ b/internal/test/fixtures/playwright.json @@ -0,0 +1,3527 @@ +{ + "config": { + "forbidOnly": false, + "fullyParallel": true, + "globalSetup": null, + "globalTeardown": null, + "globalTimeout": 0, + "grep": {}, + "grepInvert": null, + "maxFailures": 0, + "metadata": {}, + "preserveOutput": "always", + "projects": [ + { + "outputDir": "/Users/kylekthompson/src/captain-examples/playwright/test-results", + "repeatEach": 1, + "retries": 2, + "id": "chromium", + "name": "chromium", + "testDir": "/Users/kylekthompson/src/captain-examples/playwright/tests", + "testIgnore": [], + "testMatch": ["**/?(*.)@(spec|test).*"], + "timeout": 5000 + }, + { + "outputDir": "/Users/kylekthompson/src/captain-examples/playwright/test-results", + "repeatEach": 1, + "retries": 2, + "id": "firefox", + "name": "firefox", + "testDir": "/Users/kylekthompson/src/captain-examples/playwright/tests", + "testIgnore": [], + "testMatch": ["**/?(*.)@(spec|test).*"], + "timeout": 5000 + } + ], + "reporter": [ + ["html", null], + ["list", null], + [ + "json", + { + "outputFile": "test-results.json" + } + ] + ], + "reportSlowTests": { + "max": 5, + "threshold": 15000 + }, + "configFile": "/Users/kylekthompson/src/captain-examples/playwright/playwright.config.ts", + "rootDir": "/Users/kylekthompson/src/captain-examples/playwright/tests", + "quiet": false, + "shard": null, + "updateSnapshots": "missing", + "version": "1.28.1", + "workers": 5, + "webServer": null + }, + "suites": [ + { + "title": "demo-todo-app.spec.ts", + "file": "demo-todo-app.spec.ts", + "column": 0, + "line": 0, + "specs": [], + "suites": [ + { + "title": "New Todo", + "file": "demo-todo-app.spec.ts", + "line": 13, + "column": 6, + "specs": [ + { + "title": "should allow me to add todo items", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 0, + "status": "passed", + "duration": 778, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:02.753Z", + "attachments": [] + } + ], + "status": "expected" + }, + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 28, + "status": "passed", + "duration": 928, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:10.426Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "849da1cee0c412ce5534-de14671fd2e5e71cfcf4", + "file": "demo-todo-app.spec.ts", + "line": 14, + "column": 3 + }, + { + "title": "should clear text input field when an item is added", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 1, + "status": "passed", + "duration": 764, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:02.752Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "849da1cee0c412ce5534-3b8f34f2e93d2414c1ab", + "file": "demo-todo-app.spec.ts", + "line": 40, + "column": 3 + }, + { + "title": "should append new items to the bottom of the list", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 2, + "status": "passed", + "duration": 806, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:02.756Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "849da1cee0c412ce5534-2531b32b950949c93e0b", + "file": "demo-todo-app.spec.ts", + "line": 53, + "column": 3 + }, + { + "title": "should clear text input field when an item is added", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 30, + "status": "passed", + "duration": 792, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:10.655Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "849da1cee0c412ce5534-4c60951308e5d3853441", + "file": "demo-todo-app.spec.ts", + "line": 40, + "column": 3 + }, + { + "title": "should append new items to the bottom of the list", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 31, + "status": "passed", + "duration": 1103, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:11.330Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "849da1cee0c412ce5534-fa3a910e5535635607b8", + "file": "demo-todo-app.spec.ts", + "line": 53, + "column": 3 + } + ] + }, + { + "title": "Mark all as completed", + "file": "demo-todo-app.spec.ts", + "line": 72, + "column": 6, + "specs": [ + { + "title": "should allow me to mark all items as completed", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 3, + "status": "passed", + "duration": 851, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:02.754Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "849da1cee0c412ce5534-e38558bd3234b0b8d82c", + "file": "demo-todo-app.spec.ts", + "line": 82, + "column": 3 + }, + { + "title": "should allow me to clear the complete state of all items", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 4, + "status": "passed", + "duration": 882, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:02.757Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "849da1cee0c412ce5534-2e7d774e9c08aeb850d5", + "file": "demo-todo-app.spec.ts", + "line": 91, + "column": 3 + }, + { + "title": "complete all checkbox should update state when items are completed / cleared", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 1, + "status": "passed", + "duration": 446, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:03.899Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "849da1cee0c412ce5534-e5befab6d7114c3aa529", + "file": "demo-todo-app.spec.ts", + "line": 101, + "column": 3 + }, + { + "title": "should allow me to mark all items as completed", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 32, + "status": "passed", + "duration": 1278, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:11.845Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "849da1cee0c412ce5534-7e9d6193d9400cfc1cca", + "file": "demo-todo-app.spec.ts", + "line": 82, + "column": 3 + }, + { + "title": "should allow me to clear the complete state of all items", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 28, + "status": "passed", + "duration": 803, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:11.711Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "849da1cee0c412ce5534-316c9f0de23306b27464", + "file": "demo-todo-app.spec.ts", + "line": 91, + "column": 3 + }, + { + "title": "complete all checkbox should update state when items are completed / cleared", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 30, + "status": "passed", + "duration": 834, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:11.717Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "849da1cee0c412ce5534-299f354feca99d051088", + "file": "demo-todo-app.spec.ts", + "line": 101, + "column": 3 + } + ] + } + ] + }, + { + "title": "example.spec.ts", + "file": "example.spec.ts", + "column": 0, + "line": 0, + "specs": [ + { + "title": "homepage has title and links to intro page", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 0, + "status": "passed", + "duration": 607, + "errors": [], + "stdout": [ + { + "text": "some stdout\n" + }, + { + "text": "happened in here\n" + } + ], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:03.915Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-d10c8fcc34c6d8c515fb", + "file": "example.spec.ts", + "line": 3, + "column": 1 + }, + { + "title": "skipped test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "skip" + } + ], + "expectedStatus": "skipped", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 2, + "status": "skipped", + "duration": 0, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:03.944Z", + "attachments": [] + } + ], + "status": "skipped" + } + ], + "id": "a30a6eba6312f6b87ea5-64f0a773ca2a833f8d8a", + "file": "example.spec.ts", + "line": 25, + "column": 6 + }, + { + "title": "fixme test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "fixme" + } + ], + "expectedStatus": "skipped", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 2, + "status": "skipped", + "duration": 0, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:03.945Z", + "attachments": [] + } + ], + "status": "skipped" + } + ], + "id": "a30a6eba6312f6b87ea5-ca71e2f1d8eada1cf412", + "file": "example.spec.ts", + "line": 31, + "column": 6 + }, + { + "title": "failing test", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 2, + "status": "failed", + "duration": 352, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 16, + "line": 39 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n\n\u001b[90m at \u001b[39mexample.spec.ts:39\n\n\u001b[0m \u001b[90m 37 |\u001b[39m test(\u001b[32m'failing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 38 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 39 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoEqual(\u001b[36mfalse\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 40 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 41 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'throw error test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:03.945Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 16, + "line": 39 + } + }, + { + "workerIndex": 5, + "status": "failed", + "duration": 711, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 16, + "line": 39 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n\n\u001b[90m at \u001b[39mexample.spec.ts:39\n\n\u001b[0m \u001b[90m 37 |\u001b[39m test(\u001b[32m'failing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 38 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 39 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoEqual(\u001b[36mfalse\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 40 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 41 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'throw error test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:04.561Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-failing-test-chromium-retry1/trace.zip" + } + ], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 16, + "line": 39 + } + }, + { + "workerIndex": 10, + "status": "failed", + "duration": 487, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 16, + "line": 39 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n\n\u001b[90m at \u001b[39mexample.spec.ts:39\n\n\u001b[0m \u001b[90m 37 |\u001b[39m test(\u001b[32m'failing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 38 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 39 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoEqual(\u001b[36mfalse\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 40 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 41 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'throw error test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:05.655Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 16, + "line": 39 + } + } + ], + "status": "unexpected" + } + ], + "id": "a30a6eba6312f6b87ea5-711a8f3fa5bc48314300", + "file": "example.spec.ts", + "line": 37, + "column": 1 + }, + { + "title": "throw error test", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 3, + "status": "failed", + "duration": 395, + "error": { + "message": "uh oh", + "stack": "Error: uh oh\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 9, + "line": 44 + }, + "message": "Error: uh oh\n\n\u001b[90m at \u001b[39mexample.spec.ts:44\n\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'throw error test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 43 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 44 |\u001b[39m \u001b[36mthrow\u001b[39m \u001b[36mnew\u001b[39m \u001b[33mError\u001b[39m(\u001b[32m'uh oh'\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 45 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 46 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 47 |\u001b[39m test(\u001b[32m'throw string'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:03.988Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 9, + "line": 44 + } + }, + { + "workerIndex": 7, + "status": "failed", + "duration": 622, + "error": { + "message": "uh oh", + "stack": "Error: uh oh\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 9, + "line": 44 + }, + "message": "Error: uh oh\n\n\u001b[90m at \u001b[39mexample.spec.ts:44\n\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'throw error test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 43 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 44 |\u001b[39m \u001b[36mthrow\u001b[39m \u001b[36mnew\u001b[39m \u001b[33mError\u001b[39m(\u001b[32m'uh oh'\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 45 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 46 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 47 |\u001b[39m test(\u001b[32m'throw string'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:04.638Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-throw-error-test-chromium-retry1/trace.zip" + } + ], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 9, + "line": 44 + } + }, + { + "workerIndex": 11, + "status": "failed", + "duration": 496, + "error": { + "message": "uh oh", + "stack": "Error: uh oh\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 9, + "line": 44 + }, + "message": "Error: uh oh\n\n\u001b[90m at \u001b[39mexample.spec.ts:44\n\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'throw error test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 43 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 44 |\u001b[39m \u001b[36mthrow\u001b[39m \u001b[36mnew\u001b[39m \u001b[33mError\u001b[39m(\u001b[32m'uh oh'\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 45 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 46 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 47 |\u001b[39m test(\u001b[32m'throw string'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:05.655Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 9, + "line": 44 + } + } + ], + "status": "unexpected" + } + ], + "id": "a30a6eba6312f6b87ea5-4624972b460ec04d6bf4", + "file": "example.spec.ts", + "line": 42, + "column": 1 + }, + { + "title": "throw string", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 4, + "status": "failed", + "duration": 353, + "error": { + "value": "'uh oh'" + }, + "errors": [ + { + "message": "'uh oh'" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:04.020Z", + "attachments": [] + }, + { + "workerIndex": 6, + "status": "failed", + "duration": 618, + "error": { + "value": "'uh oh'" + }, + "errors": [ + { + "message": "'uh oh'" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:04.629Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-throw-string-chromium-retry1/trace.zip" + } + ] + }, + { + "workerIndex": 9, + "status": "failed", + "duration": 436, + "error": { + "value": "'uh oh'" + }, + "errors": [ + { + "message": "'uh oh'" + } + ], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:05.611Z", + "attachments": [] + } + ], + "status": "unexpected" + } + ], + "id": "a30a6eba6312f6b87ea5-84a1a1b0c94f614e6d1d", + "file": "example.spec.ts", + "line": 47, + "column": 1 + }, + { + "title": "passing test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 1, + "status": "passed", + "duration": 277, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:04.351Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-00b0e48787c875c20796", + "file": "example.spec.ts", + "line": 52, + "column": 1 + }, + { + "title": "failing w/ fail annotation", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "fail" + } + ], + "expectedStatus": "failed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 0, + "status": "failed", + "duration": 2780, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m\nReceived string: \u001b[31m\"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[39m\nCall log:\n \u001b[2m- expect.toHaveTitle with timeout 2500ms\u001b[22m\n\u001b[2m - waiting for locator(':root')\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m\nReceived string: \u001b[31m\"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[39m\nCall log:\n \u001b[2m- expect.toHaveTitle with timeout 2500ms\u001b[22m\n\u001b[2m - waiting for locator(':root')\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:79:22" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 22, + "line": 79 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m\nReceived string: \u001b[31m\"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[39m\nCall log:\n \u001b[2m- expect.toHaveTitle with timeout 2500ms\u001b[22m\n\u001b[2m - waiting for locator(':root')\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\n\n\u001b[90m at \u001b[39mexample.spec.ts:79\n\n\u001b[0m \u001b[90m 77 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 78 |\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 79 |\u001b[39m \u001b[36mawait\u001b[39m expect(page)\u001b[33m.\u001b[39mtoHaveTitle(\u001b[35m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 80 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 81 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 82 |\u001b[39m test(\u001b[32m'passing w/ fail annotation'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:79:22\u001b[22m" + } + ], + "stdout": [], + "stderr": [ + { + "text": "some stderr\n" + }, + { + "text": "happened in here\n" + } + ], + "retry": 0, + "startTime": "2022-12-15T20:31:04.841Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 22, + "line": 79 + } + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-adc18b0bef59a0019f93", + "file": "example.spec.ts", + "line": 71, + "column": 1 + }, + { + "title": "passing w/ fail annotation", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "fail" + } + ], + "expectedStatus": "failed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 1, + "status": "passed", + "duration": 383, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:04.964Z", + "attachments": [] + }, + { + "workerIndex": 8, + "status": "passed", + "duration": 573, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:05.598Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-passing-w-fail-annotation-chromium-retry1/trace.zip" + } + ] + }, + { + "workerIndex": 15, + "status": "passed", + "duration": 415, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:06.587Z", + "attachments": [] + } + ], + "status": "unexpected" + } + ], + "id": "a30a6eba6312f6b87ea5-64defaa9e93b6ce1ba34", + "file": "example.spec.ts", + "line": 82, + "column": 1 + }, + { + "title": "fails then passes", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 12, + "status": "failed", + "duration": 145, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:91:26" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 26, + "line": 91 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n\u001b[90m at \u001b[39mexample.spec.ts:91\n\n\u001b[0m \u001b[90m 89 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 90 |\u001b[39m test(\u001b[32m'fails then passes'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }\u001b[33m,\u001b[39m testInfo) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 91 |\u001b[39m expect(testInfo\u001b[33m.\u001b[39mretry)\u001b[33m.\u001b[39mtoEqual(\u001b[35m2\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 92 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 93 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 94 |\u001b[39m test(\u001b[32m'times out'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:91:26\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:06.407Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 26, + "line": 91 + } + }, + { + "workerIndex": 16, + "status": "failed", + "duration": 138, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:91:26" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 26, + "line": 91 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m\n\n\u001b[90m at \u001b[39mexample.spec.ts:91\n\n\u001b[0m \u001b[90m 89 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 90 |\u001b[39m test(\u001b[32m'fails then passes'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }\u001b[33m,\u001b[39m testInfo) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 91 |\u001b[39m expect(testInfo\u001b[33m.\u001b[39mretry)\u001b[33m.\u001b[39mtoEqual(\u001b[35m2\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 92 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 93 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 94 |\u001b[39m test(\u001b[32m'times out'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:91:26\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:06.966Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-fails-then-passes-chromium-retry1/trace.zip" + } + ], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 26, + "line": 91 + } + }, + { + "workerIndex": 18, + "status": "passed", + "duration": 158, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:07.441Z", + "attachments": [] + } + ], + "status": "flaky" + } + ], + "id": "a30a6eba6312f6b87ea5-9b579720b8e630328b68", + "file": "example.spec.ts", + "line": 90, + "column": 1 + }, + { + "title": "times out", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 1000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 13, + "status": "timedOut", + "duration": 1001, + "error": { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "errors": [ + { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:06.525Z", + "attachments": [] + }, + { + "workerIndex": 19, + "status": "timedOut", + "duration": 1000, + "error": { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "errors": [ + { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:07.920Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-times-out-chromium-retry1/trace.zip" + } + ] + }, + { + "workerIndex": 25, + "status": "timedOut", + "duration": 1001, + "error": { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "errors": [ + { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + } + ], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:09.293Z", + "attachments": [] + } + ], + "status": "unexpected" + } + ], + "id": "a30a6eba6312f6b87ea5-e6eef2819376b5ccda10", + "file": "example.spec.ts", + "line": 94, + "column": 1 + }, + { + "title": "slow", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 14, + "status": "passed", + "duration": 1658, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:06.555Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-252bdbec1470e87fad0a", + "file": "example.spec.ts", + "line": 99, + "column": 1 + }, + { + "title": "homepage has title and links to intro page", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 28, + "status": "passed", + "duration": 598, + "errors": [], + "stdout": [ + { + "text": "some stdout\n" + }, + { + "text": "happened in here\n" + } + ], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:12.518Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-c069cff41dac3887fe00", + "file": "example.spec.ts", + "line": 3, + "column": 1 + }, + { + "title": "skipped test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "skip" + } + ], + "expectedStatus": "skipped", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 30, + "status": "skipped", + "duration": 0, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:12.554Z", + "attachments": [] + } + ], + "status": "skipped" + } + ], + "id": "a30a6eba6312f6b87ea5-0e4b9e5d7d498ee35b9b", + "file": "example.spec.ts", + "line": 25, + "column": 6 + }, + { + "title": "fixme test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "fixme" + } + ], + "expectedStatus": "skipped", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 30, + "status": "skipped", + "duration": 0, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:12.555Z", + "attachments": [] + } + ], + "status": "skipped" + } + ], + "id": "a30a6eba6312f6b87ea5-3dcf5d891e098fcdfcc6", + "file": "example.spec.ts", + "line": 31, + "column": 6 + }, + { + "title": "failing test", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 30, + "status": "failed", + "duration": 407, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 16, + "line": 39 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n\n\u001b[90m at \u001b[39mexample.spec.ts:39\n\n\u001b[0m \u001b[90m 37 |\u001b[39m test(\u001b[32m'failing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 38 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 39 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoEqual(\u001b[36mfalse\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 40 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 41 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'throw error test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:12.555Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 16, + "line": 39 + } + }, + { + "workerIndex": 36, + "status": "failed", + "duration": 1077, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 16, + "line": 39 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n\n\u001b[90m at \u001b[39mexample.spec.ts:39\n\n\u001b[0m \u001b[90m 37 |\u001b[39m test(\u001b[32m'failing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 38 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 39 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoEqual(\u001b[36mfalse\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 40 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 41 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'throw error test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:14.175Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-failing-test-firefox-retry1/trace.zip" + } + ], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 16, + "line": 39 + } + }, + { + "workerIndex": 40, + "status": "failed", + "duration": 1008, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 16, + "line": 39 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n\n\u001b[90m at \u001b[39mexample.spec.ts:39\n\n\u001b[0m \u001b[90m 37 |\u001b[39m test(\u001b[32m'failing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 38 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 39 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoEqual(\u001b[36mfalse\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 40 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 41 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'throw error test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:39:16\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:17.700Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 16, + "line": 39 + } + } + ], + "status": "unexpected" + } + ], + "id": "a30a6eba6312f6b87ea5-5287ad25fe83601998b9", + "file": "example.spec.ts", + "line": 37, + "column": 1 + }, + { + "title": "throw error test", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 31, + "status": "failed", + "duration": 459, + "error": { + "message": "uh oh", + "stack": "Error: uh oh\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 9, + "line": 44 + }, + "message": "Error: uh oh\n\n\u001b[90m at \u001b[39mexample.spec.ts:44\n\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'throw error test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 43 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 44 |\u001b[39m \u001b[36mthrow\u001b[39m \u001b[36mnew\u001b[39m \u001b[33mError\u001b[39m(\u001b[32m'uh oh'\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 45 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 46 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 47 |\u001b[39m test(\u001b[32m'throw string'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:12.720Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 9, + "line": 44 + } + }, + { + "workerIndex": 37, + "status": "failed", + "duration": 873, + "error": { + "message": "uh oh", + "stack": "Error: uh oh\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 9, + "line": 44 + }, + "message": "Error: uh oh\n\n\u001b[90m at \u001b[39mexample.spec.ts:44\n\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'throw error test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 43 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 44 |\u001b[39m \u001b[36mthrow\u001b[39m \u001b[36mnew\u001b[39m \u001b[33mError\u001b[39m(\u001b[32m'uh oh'\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 45 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 46 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 47 |\u001b[39m test(\u001b[32m'throw string'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:14.894Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-throw-error-test-firefox-retry1/trace.zip" + } + ], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 9, + "line": 44 + } + }, + { + "workerIndex": 42, + "status": "failed", + "duration": 758, + "error": { + "message": "uh oh", + "stack": "Error: uh oh\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 9, + "line": 44 + }, + "message": "Error: uh oh\n\n\u001b[90m at \u001b[39mexample.spec.ts:44\n\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'throw error test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 43 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 44 |\u001b[39m \u001b[36mthrow\u001b[39m \u001b[36mnew\u001b[39m \u001b[33mError\u001b[39m(\u001b[32m'uh oh'\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 45 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 46 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 47 |\u001b[39m test(\u001b[32m'throw string'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:44:9\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:18.483Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 9, + "line": 44 + } + } + ], + "status": "unexpected" + } + ], + "id": "a30a6eba6312f6b87ea5-d200cfcb510a7218c844", + "file": "example.spec.ts", + "line": 42, + "column": 1 + }, + { + "title": "throw string", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 28, + "status": "failed", + "duration": 392, + "error": { + "value": "'uh oh'" + }, + "errors": [ + { + "message": "'uh oh'" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:13.117Z", + "attachments": [] + }, + { + "workerIndex": 35, + "status": "failed", + "duration": 1089, + "error": { + "value": "'uh oh'" + }, + "errors": [ + { + "message": "'uh oh'" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:14.046Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-throw-string-firefox-retry1/trace.zip" + } + ] + }, + { + "workerIndex": 39, + "status": "failed", + "duration": 855, + "error": { + "value": "'uh oh'" + }, + "errors": [ + { + "message": "'uh oh'" + } + ], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:17.565Z", + "attachments": [] + } + ], + "status": "unexpected" + } + ], + "id": "a30a6eba6312f6b87ea5-a41f0eabd54bdd770d4c", + "file": "example.spec.ts", + "line": 47, + "column": 1 + }, + { + "title": "passing test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 34, + "status": "passed", + "duration": 829, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:13.491Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-2a2677de01dea4595202", + "file": "example.spec.ts", + "line": 52, + "column": 1 + }, + { + "title": "failing w/ fail annotation", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "fail" + } + ], + "expectedStatus": "failed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 32, + "status": "failed", + "duration": 2929, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m\nReceived string: \u001b[31m\"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[39m\nCall log:\n \u001b[2m- expect.toHaveTitle with timeout 2500ms\u001b[22m\n\u001b[2m - waiting for locator(':root')\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m\nReceived string: \u001b[31m\"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[39m\nCall log:\n \u001b[2m- expect.toHaveTitle with timeout 2500ms\u001b[22m\n\u001b[2m - waiting for locator(':root')\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:79:22" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 22, + "line": 79 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m\nReceived string: \u001b[31m\"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[39m\nCall log:\n \u001b[2m- expect.toHaveTitle with timeout 2500ms\u001b[22m\n\u001b[2m - waiting for locator(':root')\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\n\n\u001b[90m at \u001b[39mexample.spec.ts:79\n\n\u001b[0m \u001b[90m 77 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 78 |\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 79 |\u001b[39m \u001b[36mawait\u001b[39m expect(page)\u001b[33m.\u001b[39mtoHaveTitle(\u001b[35m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 80 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 81 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 82 |\u001b[39m test(\u001b[32m'passing w/ fail annotation'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:79:22\u001b[22m" + } + ], + "stdout": [], + "stderr": [ + { + "text": "some stderr\n" + }, + { + "text": "happened in here\n" + } + ], + "retry": 0, + "startTime": "2022-12-15T20:31:14.227Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 22, + "line": 79 + } + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-7dc0006774c808b9170d", + "file": "example.spec.ts", + "line": 71, + "column": 1 + }, + { + "title": "passing w/ fail annotation", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "fail" + } + ], + "expectedStatus": "failed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 34, + "status": "passed", + "duration": 498, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:14.607Z", + "attachments": [] + }, + { + "workerIndex": 38, + "status": "passed", + "duration": 1024, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:17.007Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-passing-w-fail-annotation-firefox-retry1/trace.zip" + } + ] + }, + { + "workerIndex": 43, + "status": "passed", + "duration": 824, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:20.474Z", + "attachments": [] + } + ], + "status": "unexpected" + } + ], + "id": "a30a6eba6312f6b87ea5-f7ae9ddfc175c50ba5d6", + "file": "example.spec.ts", + "line": 82, + "column": 1 + }, + { + "title": "fails then passes", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 32, + "status": "failed", + "duration": 113, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:91:26" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 26, + "line": 91 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n\u001b[90m at \u001b[39mexample.spec.ts:91\n\n\u001b[0m \u001b[90m 89 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 90 |\u001b[39m test(\u001b[32m'fails then passes'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }\u001b[33m,\u001b[39m testInfo) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 91 |\u001b[39m expect(testInfo\u001b[33m.\u001b[39mretry)\u001b[33m.\u001b[39mtoEqual(\u001b[35m2\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 92 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 93 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 94 |\u001b[39m test(\u001b[32m'times out'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:91:26\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:17.156Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 26, + "line": 91 + } + }, + { + "workerIndex": 41, + "status": "failed", + "duration": 568, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:91:26" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 26, + "line": 91 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m\n\n\u001b[90m at \u001b[39mexample.spec.ts:91\n\n\u001b[0m \u001b[90m 89 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 90 |\u001b[39m test(\u001b[32m'fails then passes'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }\u001b[33m,\u001b[39m testInfo) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 91 |\u001b[39m expect(testInfo\u001b[33m.\u001b[39mretry)\u001b[33m.\u001b[39mtoEqual(\u001b[35m2\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 92 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 93 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 94 |\u001b[39m test(\u001b[32m'times out'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts:91:26\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:17.784Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-fails-then-passes-firefox-retry1/trace.zip" + } + ], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/example.spec.ts", + "column": 26, + "line": 91 + } + }, + { + "workerIndex": 46, + "status": "passed", + "duration": 464, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:21.361Z", + "attachments": [] + } + ], + "status": "flaky" + } + ], + "id": "a30a6eba6312f6b87ea5-86e8260f564936034b17", + "file": "example.spec.ts", + "line": 90, + "column": 1 + }, + { + "title": "times out", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 1000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 44, + "status": "timedOut", + "duration": 1001, + "error": { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "errors": [ + { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:21.097Z", + "attachments": [] + }, + { + "workerIndex": 49, + "status": "timedOut", + "duration": 1000, + "error": { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "errors": [ + { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:24.612Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/example-times-out-firefox-retry1/trace.zip" + } + ] + }, + { + "workerIndex": 53, + "status": "timedOut", + "duration": 1000, + "error": { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "errors": [ + { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + } + ], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:28.119Z", + "attachments": [] + } + ], + "status": "unexpected" + } + ], + "id": "a30a6eba6312f6b87ea5-6c41828baf4166557f52", + "file": "example.spec.ts", + "line": 94, + "column": 1 + }, + { + "title": "slow", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 45, + "status": "passed", + "duration": 1974, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:21.245Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-9adb266faf9a6de32e0a", + "file": "example.spec.ts", + "line": 99, + "column": 1 + } + ], + "suites": [ + { + "title": "first level", + "file": "example.spec.ts", + "line": 57, + "column": 6, + "specs": [ + { + "title": "passing test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 0, + "status": "passed", + "duration": 316, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:04.523Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-1c988810f6ab8238fc73", + "file": "example.spec.ts", + "line": 58, + "column": 3 + }, + { + "title": "passing test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 32, + "status": "passed", + "duration": 413, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:13.465Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-e65c153d4b90fde4b4b1", + "file": "example.spec.ts", + "line": 58, + "column": 3 + } + ], + "suites": [ + { + "title": "second level", + "file": "example.spec.ts", + "line": 63, + "column": 8, + "specs": [ + { + "title": "passing test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 1, + "status": "passed", + "duration": 334, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:04.629Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-4fe3d6c91823d179c000", + "file": "example.spec.ts", + "line": 64, + "column": 5 + }, + { + "title": "passing test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 32, + "status": "passed", + "duration": 346, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:13.880Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "a30a6eba6312f6b87ea5-37141e45a5ece481efcf", + "file": "example.spec.ts", + "line": 64, + "column": 5 + } + ] + } + ] + } + ] + }, + { + "title": "nested/example.spec.ts", + "file": "nested/example.spec.ts", + "column": 0, + "line": 0, + "specs": [ + { + "title": "homepage has title and links to intro page", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 17, + "status": "passed", + "duration": 573, + "errors": [], + "stdout": [ + { + "text": "some stdout\n" + }, + { + "text": "happened in here\n" + } + ], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:07.335Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "b00d8487c32b2f22b924-1d8d833506c19660e214", + "file": "nested/example.spec.ts", + "line": 3, + "column": 1 + }, + { + "title": "skipped test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "skip" + } + ], + "expectedStatus": "skipped", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 0, + "status": "skipped", + "duration": 0, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:07.624Z", + "attachments": [] + } + ], + "status": "skipped" + } + ], + "id": "b00d8487c32b2f22b924-16d3e4bbe656c68cebec", + "file": "nested/example.spec.ts", + "line": 25, + "column": 6 + }, + { + "title": "fixme test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "fixme" + } + ], + "expectedStatus": "skipped", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 0, + "status": "skipped", + "duration": 0, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:07.625Z", + "attachments": [] + } + ], + "status": "skipped" + } + ], + "id": "b00d8487c32b2f22b924-843eb6a409e0c8d80d7f", + "file": "nested/example.spec.ts", + "line": 31, + "column": 6 + }, + { + "title": "failing test", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 0, + "status": "failed", + "duration": 310, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 16, + "line": 39 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n\n\u001b[90m at \u001b[39mnested/example.spec.ts:39\n\n\u001b[0m \u001b[90m 37 |\u001b[39m test(\u001b[32m'failing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 38 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 39 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoEqual(\u001b[36mfalse\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 40 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 41 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'passing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:07.626Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 16, + "line": 39 + } + }, + { + "workerIndex": 20, + "status": "failed", + "duration": 519, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 16, + "line": 39 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n\n\u001b[90m at \u001b[39mnested/example.spec.ts:39\n\n\u001b[0m \u001b[90m 37 |\u001b[39m test(\u001b[32m'failing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 38 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 39 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoEqual(\u001b[36mfalse\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 40 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 41 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'passing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:08.194Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-failing-test-chromium-retry1/trace.zip" + } + ], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 16, + "line": 39 + } + }, + { + "workerIndex": 23, + "status": "failed", + "duration": 435, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 16, + "line": 39 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n\n\u001b[90m at \u001b[39mnested/example.spec.ts:39\n\n\u001b[0m \u001b[90m 37 |\u001b[39m test(\u001b[32m'failing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 38 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 39 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoEqual(\u001b[36mfalse\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 40 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 41 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'passing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:09.063Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 16, + "line": 39 + } + } + ], + "status": "unexpected" + } + ], + "id": "b00d8487c32b2f22b924-378a825e5dd92a16869c", + "file": "nested/example.spec.ts", + "line": 37, + "column": 1 + }, + { + "title": "passing test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 18, + "status": "passed", + "duration": 344, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:07.695Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "b00d8487c32b2f22b924-ca5c30892f889287a9ec", + "file": "nested/example.spec.ts", + "line": 42, + "column": 1 + }, + { + "title": "failing w/ fail annotation", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "fail" + } + ], + "expectedStatus": "failed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 17, + "status": "failed", + "duration": 2800, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m\nReceived string: \u001b[31m\"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[39m\nCall log:\n \u001b[2m- expect.toHaveTitle with timeout 2500ms\u001b[22m\n\u001b[2m - waiting for locator(':root')\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m\nReceived string: \u001b[31m\"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[39m\nCall log:\n \u001b[2m- expect.toHaveTitle with timeout 2500ms\u001b[22m\n\u001b[2m - waiting for locator(':root')\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\n at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:69:22" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 22, + "line": 69 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m\nReceived string: \u001b[31m\"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[39m\nCall log:\n \u001b[2m- expect.toHaveTitle with timeout 2500ms\u001b[22m\n\u001b[2m - waiting for locator(':root')\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\n\n\u001b[90m at \u001b[39mnested/example.spec.ts:69\n\n\u001b[0m \u001b[90m 67 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 68 |\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 69 |\u001b[39m \u001b[36mawait\u001b[39m expect(page)\u001b[33m.\u001b[39mtoHaveTitle(\u001b[35m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 70 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 71 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 72 |\u001b[39m test(\u001b[32m'passing w/ fail annotation'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:69:22\u001b[22m" + } + ], + "stdout": [], + "stderr": [ + { + "text": "some stderr\n" + }, + { + "text": "happened in here\n" + } + ], + "retry": 0, + "startTime": "2022-12-15T20:31:08.278Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 22, + "line": 69 + } + } + ], + "status": "expected" + } + ], + "id": "b00d8487c32b2f22b924-562fa55fec6bc311d5ec", + "file": "nested/example.spec.ts", + "line": 61, + "column": 1 + }, + { + "title": "passing w/ fail annotation", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "fail" + } + ], + "expectedStatus": "failed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 18, + "status": "passed", + "duration": 296, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:08.312Z", + "attachments": [] + }, + { + "workerIndex": 22, + "status": "passed", + "duration": 481, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:08.860Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-passing-w-fail-annotation-chromium-retry1/trace.zip" + } + ] + }, + { + "workerIndex": 26, + "status": "passed", + "duration": 386, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:09.692Z", + "attachments": [] + } + ], + "status": "unexpected" + } + ], + "id": "b00d8487c32b2f22b924-658d5ddb05b5ec53315f", + "file": "nested/example.spec.ts", + "line": 72, + "column": 1 + }, + { + "title": "fails then passes", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 14, + "status": "failed", + "duration": 53, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:81:26" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 26, + "line": 81 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n\u001b[90m at \u001b[39mnested/example.spec.ts:81\n\n\u001b[0m \u001b[90m 79 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 80 |\u001b[39m test(\u001b[32m'fails then passes'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }\u001b[33m,\u001b[39m testInfo) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 81 |\u001b[39m expect(testInfo\u001b[33m.\u001b[39mretry)\u001b[33m.\u001b[39mtoEqual(\u001b[35m2\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 82 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 83 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 84 |\u001b[39m test(\u001b[32m'times out'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:81:26\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:08.317Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 26, + "line": 81 + } + }, + { + "workerIndex": 21, + "status": "failed", + "duration": 163, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:81:26" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 26, + "line": 81 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m\n\n\u001b[90m at \u001b[39mnested/example.spec.ts:81\n\n\u001b[0m \u001b[90m 79 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 80 |\u001b[39m test(\u001b[32m'fails then passes'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }\u001b[33m,\u001b[39m testInfo) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 81 |\u001b[39m expect(testInfo\u001b[33m.\u001b[39mretry)\u001b[33m.\u001b[39mtoEqual(\u001b[35m2\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 82 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 83 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 84 |\u001b[39m test(\u001b[32m'times out'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:81:26\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:08.620Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-fails-then-passes-chromium-retry1/trace.zip" + } + ], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 26, + "line": 81 + } + }, + { + "workerIndex": 24, + "status": "passed", + "duration": 152, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:09.134Z", + "attachments": [] + } + ], + "status": "flaky" + } + ], + "id": "b00d8487c32b2f22b924-3402e197710464b6f85f", + "file": "nested/example.spec.ts", + "line": 80, + "column": 1 + }, + { + "title": "times out", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 1000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 24, + "status": "timedOut", + "duration": 1000, + "error": { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "errors": [ + { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:09.386Z", + "attachments": [] + }, + { + "workerIndex": 29, + "status": "timedOut", + "duration": 999, + "error": { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "errors": [ + { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:10.628Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-times-out-chromium-retry1/trace.zip" + } + ] + }, + { + "workerIndex": 33, + "status": "timedOut", + "duration": 1000, + "error": { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "errors": [ + { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + } + ], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:12.072Z", + "attachments": [] + } + ], + "status": "unexpected" + } + ], + "id": "b00d8487c32b2f22b924-e5543a80b3fee44eab7b", + "file": "nested/example.spec.ts", + "line": 84, + "column": 1 + }, + { + "title": "slow", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 27, + "status": "passed", + "duration": 1625, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:09.858Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "b00d8487c32b2f22b924-32f014b3d0927d68f1d2", + "file": "nested/example.spec.ts", + "line": 89, + "column": 1 + }, + { + "title": "homepage has title and links to intro page", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 47, + "status": "passed", + "duration": 899, + "errors": [], + "stdout": [ + { + "text": "some stdout\n" + }, + { + "text": "happened in here\n" + } + ], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:22.074Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "b00d8487c32b2f22b924-6bfc86ff093e923796bd", + "file": "nested/example.spec.ts", + "line": 3, + "column": 1 + }, + { + "title": "skipped test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "skip" + } + ], + "expectedStatus": "skipped", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 46, + "status": "skipped", + "duration": 0, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:22.115Z", + "attachments": [] + } + ], + "status": "skipped" + } + ], + "id": "b00d8487c32b2f22b924-0100a51cbd5a421e3133", + "file": "nested/example.spec.ts", + "line": 25, + "column": 6 + }, + { + "title": "fixme test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "fixme" + } + ], + "expectedStatus": "skipped", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 46, + "status": "skipped", + "duration": 0, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:22.118Z", + "attachments": [] + } + ], + "status": "skipped" + } + ], + "id": "b00d8487c32b2f22b924-8d194d66845a9e772c9f", + "file": "nested/example.spec.ts", + "line": 31, + "column": 6 + }, + { + "title": "failing test", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 46, + "status": "failed", + "duration": 536, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 16, + "line": 39 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n\n\u001b[90m at \u001b[39mnested/example.spec.ts:39\n\n\u001b[0m \u001b[90m 37 |\u001b[39m test(\u001b[32m'failing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 38 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 39 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoEqual(\u001b[36mfalse\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 40 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 41 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'passing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:22.119Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 16, + "line": 39 + } + }, + { + "workerIndex": 51, + "status": "failed", + "duration": 878, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 16, + "line": 39 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n\n\u001b[90m at \u001b[39mnested/example.spec.ts:39\n\n\u001b[0m \u001b[90m 37 |\u001b[39m test(\u001b[32m'failing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 38 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 39 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoEqual(\u001b[36mfalse\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 40 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 41 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'passing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:24.887Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-failing-test-firefox-retry1/trace.zip" + } + ], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 16, + "line": 39 + } + }, + { + "workerIndex": 56, + "status": "failed", + "duration": 826, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 16, + "line": 39 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mtrue\u001b[39m\n\n\u001b[90m at \u001b[39mnested/example.spec.ts:39\n\n\u001b[0m \u001b[90m 37 |\u001b[39m test(\u001b[32m'failing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m 38 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 39 |\u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoEqual(\u001b[36mfalse\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 40 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 41 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 42 |\u001b[39m test(\u001b[32m'passing test'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:39:16\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:28.468Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 16, + "line": 39 + } + } + ], + "status": "unexpected" + } + ], + "id": "b00d8487c32b2f22b924-2b11adfca9b0f6f7a914", + "file": "nested/example.spec.ts", + "line": 37, + "column": 1 + }, + { + "title": "passing test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 47, + "status": "passed", + "duration": 573, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:23.254Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "b00d8487c32b2f22b924-27ae8d680ca67aa4fbed", + "file": "nested/example.spec.ts", + "line": 42, + "column": 1 + }, + { + "title": "failing w/ fail annotation", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "fail" + } + ], + "expectedStatus": "failed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 47, + "status": "failed", + "duration": 2869, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m\nReceived string: \u001b[31m\"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[39m\nCall log:\n \u001b[2m- expect.toHaveTitle with timeout 2500ms\u001b[22m\n\u001b[2m - waiting for locator(':root')\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m\nReceived string: \u001b[31m\"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[39m\nCall log:\n \u001b[2m- expect.toHaveTitle with timeout 2500ms\u001b[22m\n\u001b[2m - waiting for locator(':root')\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\n at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:69:22" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 22, + "line": 69 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m\nReceived string: \u001b[31m\"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[39m\nCall log:\n \u001b[2m- expect.toHaveTitle with timeout 2500ms\u001b[22m\n\u001b[2m - waiting for locator(':root')\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\u001b[2m - locator resolved to …\u001b[22m\n\u001b[2m - unexpected value \"Fast and reliable end-to-end testing for modern web apps | Playwright\"\u001b[22m\n\n\n\u001b[90m at \u001b[39mnested/example.spec.ts:69\n\n\u001b[0m \u001b[90m 67 |\u001b[39m \u001b[36mawait\u001b[39m page\u001b[33m.\u001b[39mgoto(\u001b[32m'https://playwright.dev/'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 68 |\u001b[39m\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 69 |\u001b[39m \u001b[36mawait\u001b[39m expect(page)\u001b[33m.\u001b[39mtoHaveTitle(\u001b[35m/asldkhjflkasjhdfkajhsdlfkjh/\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 70 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 71 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 72 |\u001b[39m test(\u001b[32m'passing w/ fail annotation'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:69:22\u001b[22m" + } + ], + "stdout": [], + "stderr": [ + { + "text": "some stderr\n" + }, + { + "text": "happened in here\n" + } + ], + "retry": 0, + "startTime": "2022-12-15T20:31:23.828Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 22, + "line": 69 + } + } + ], + "status": "expected" + } + ], + "id": "b00d8487c32b2f22b924-b8298140f477a8747c40", + "file": "nested/example.spec.ts", + "line": 61, + "column": 1 + }, + { + "title": "passing w/ fail annotation", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [ + { + "type": "fail" + } + ], + "expectedStatus": "failed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 45, + "status": "passed", + "duration": 386, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:23.947Z", + "attachments": [] + }, + { + "workerIndex": 50, + "status": "passed", + "duration": 945, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:24.843Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-passing-w-fail-annotation-firefox-retry1/trace.zip" + } + ] + }, + { + "workerIndex": 55, + "status": "passed", + "duration": 896, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:28.374Z", + "attachments": [] + } + ], + "status": "unexpected" + } + ], + "id": "b00d8487c32b2f22b924-ffff18f4a74ab56e4f52", + "file": "nested/example.spec.ts", + "line": 72, + "column": 1 + }, + { + "title": "fails then passes", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 48, + "status": "failed", + "duration": 139, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:81:26" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 26, + "line": 81 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n\u001b[90m at \u001b[39mnested/example.spec.ts:81\n\n\u001b[0m \u001b[90m 79 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 80 |\u001b[39m test(\u001b[32m'fails then passes'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }\u001b[33m,\u001b[39m testInfo) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 81 |\u001b[39m expect(testInfo\u001b[33m.\u001b[39mretry)\u001b[33m.\u001b[39mtoEqual(\u001b[35m2\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 82 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 83 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 84 |\u001b[39m test(\u001b[32m'times out'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:81:26\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:25.064Z", + "attachments": [], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 26, + "line": 81 + } + }, + { + "workerIndex": 52, + "status": "failed", + "duration": 459, + "error": { + "message": "\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m", + "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m\n at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:81:26" + }, + "errors": [ + { + "location": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 26, + "line": 81 + }, + "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m1\u001b[39m\n\n\u001b[90m at \u001b[39mnested/example.spec.ts:81\n\n\u001b[0m \u001b[90m 79 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 80 |\u001b[39m test(\u001b[32m'fails then passes'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }\u001b[33m,\u001b[39m testInfo) \u001b[33m=>\u001b[39m {\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 81 |\u001b[39m expect(testInfo\u001b[33m.\u001b[39mretry)\u001b[33m.\u001b[39mtoEqual(\u001b[35m2\u001b[39m)\u001b[0m\n\u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 82 |\u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 83 |\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 84 |\u001b[39m test(\u001b[32m'times out'\u001b[39m\u001b[33m,\u001b[39m \u001b[36masync\u001b[39m ({ page }) \u001b[33m=>\u001b[39m {\u001b[0m\n\n\u001b[2m at /Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts:81:26\u001b[22m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:27.484Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-fails-then-passes-firefox-retry1/trace.zip" + } + ], + "errorLocation": { + "file": "/Users/kylekthompson/src/captain-examples/playwright/tests/nested/example.spec.ts", + "column": 26, + "line": 81 + } + }, + { + "workerIndex": 57, + "status": "passed", + "duration": 380, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:30.930Z", + "attachments": [] + } + ], + "status": "flaky" + } + ], + "id": "b00d8487c32b2f22b924-c13e9183b9ad2d8dde13", + "file": "nested/example.spec.ts", + "line": 80, + "column": 1 + }, + { + "title": "times out", + "ok": false, + "tags": [], + "tests": [ + { + "timeout": 1000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 47, + "status": "timedOut", + "duration": 1000, + "error": { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "errors": [ + { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + } + ], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:26.698Z", + "attachments": [] + }, + { + "workerIndex": 54, + "status": "timedOut", + "duration": 1000, + "error": { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "errors": [ + { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + } + ], + "stdout": [], + "stderr": [], + "retry": 1, + "startTime": "2022-12-15T20:31:28.204Z", + "attachments": [ + { + "name": "trace", + "contentType": "application/zip", + "path": "/Users/kylekthompson/src/captain-examples/playwright/test-results/nested-example-times-out-firefox-retry1/trace.zip" + } + ] + }, + { + "workerIndex": 59, + "status": "timedOut", + "duration": 1001, + "error": { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + }, + "errors": [ + { + "message": "\u001b[31mTest timeout of 1000ms exceeded.\u001b[39m" + } + ], + "stdout": [], + "stderr": [], + "retry": 2, + "startTime": "2022-12-15T20:31:31.758Z", + "attachments": [] + } + ], + "status": "unexpected" + } + ], + "id": "b00d8487c32b2f22b924-f7d80085b3e2ddb600d5", + "file": "nested/example.spec.ts", + "line": 84, + "column": 1 + }, + { + "title": "slow", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 58, + "status": "passed", + "duration": 1857, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:31.648Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "b00d8487c32b2f22b924-17d1c1ef88345e89ae10", + "file": "nested/example.spec.ts", + "line": 89, + "column": 1 + } + ], + "suites": [ + { + "title": "first level", + "file": "nested/example.spec.ts", + "line": 47, + "column": 6, + "specs": [ + { + "title": "passing test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 17, + "status": "passed", + "duration": 269, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:08.008Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "b00d8487c32b2f22b924-b79576c33c2fa364f80d", + "file": "nested/example.spec.ts", + "line": 48, + "column": 3 + }, + { + "title": "passing test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 45, + "status": "passed", + "duration": 435, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:23.511Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "b00d8487c32b2f22b924-b8d1e74a876345109788", + "file": "nested/example.spec.ts", + "line": 48, + "column": 3 + } + ], + "suites": [ + { + "title": "second level", + "file": "nested/example.spec.ts", + "line": 53, + "column": 8, + "specs": [ + { + "title": "passing test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [ + { + "workerIndex": 18, + "status": "passed", + "duration": 269, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:08.041Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "b00d8487c32b2f22b924-d364371342698455e8ca", + "file": "nested/example.spec.ts", + "line": 54, + "column": 5 + }, + { + "title": "passing test", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 5000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [ + { + "workerIndex": 48, + "status": "passed", + "duration": 813, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2022-12-15T20:31:23.975Z", + "attachments": [] + } + ], + "status": "expected" + } + ], + "id": "b00d8487c32b2f22b924-bfe118549b6463f6b2ec", + "file": "nested/example.spec.ts", + "line": 54, + "column": 5 + } + ] + } + ] + } + ] + } + ], + "errors": [] +} diff --git a/internal/test/fixtures/playwright_global_other_error.json b/internal/test/fixtures/playwright_global_other_error.json new file mode 100644 index 0000000..9e3fd9c --- /dev/null +++ b/internal/test/fixtures/playwright_global_other_error.json @@ -0,0 +1,494 @@ +{ + "config": { + "forbidOnly": true, + "fullyParallel": true, + "globalSetup": "/__w/captain/captain/playwright/global-setup.ts", + "globalTeardown": null, + "globalTimeout": 0, + "grep": {}, + "grepInvert": null, + "maxFailures": 0, + "metadata": {}, + "preserveOutput": "always", + "projects": [ + { + "outputDir": "/__w/captain/captain/test-results", + "repeatEach": 1, + "retries": 0, + "id": "chromium", + "name": "chromium", + "testDir": "/__w/captain/captain/playwright/tests", + "testIgnore": [], + "testMatch": [ + "**/?(*.)@(spec|test).*" + ], + "timeout": 30000 + } + ], + "reporter": [ + [ + "html", + null + ], + [ + "json", + { + "outputFile": "test-results.json" + } + ] + ], + "reportSlowTests": { + "max": 5, + "threshold": 15000 + }, + "configFile": "/__w/captain/captain/playwright.config.ts", + "rootDir": "/__w/captain/captain/playwright/tests", + "quiet": false, + "shard": null, + "updateSnapshots": "missing", + "version": "1.30.0", + "workers": 1, + "webServer": null + }, + "suites": [ + { + "title": "auth.spec.ts", + "file": "auth.spec.ts", + "column": 0, + "line": 0, + "specs": [], + "suites": [ + { + "title": "Auth Flow", + "file": "auth.spec.ts", + "line": 8, + "column": 6, + "specs": [ + { + "title": "You can signup and login with GitHub", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "d748ac400d08b85935ef-6ea3e65eeb3e4b7faf86", + "file": "auth.spec.ts", + "line": 9, + "column": 7 + } + ] + } + ] + }, + { + "title": "deep-link.spec.ts", + "file": "deep-link.spec.ts", + "column": 0, + "line": 0, + "specs": [], + "suites": [ + { + "title": "Deep links", + "file": "deep-link.spec.ts", + "line": 10, + "column": 6, + "specs": [ + { + "title": "work across domains when choosing an organization", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "7b3622b94d166c44c4f8-d2fcafd16a18e8f62f2b", + "file": "deep-link.spec.ts", + "line": 11, + "column": 7 + } + ] + } + ] + }, + { + "title": "feedback.spec.ts", + "file": "feedback.spec.ts", + "column": 0, + "line": 0, + "specs": [], + "suites": [ + { + "title": "Feedback submission functionality", + "file": "feedback.spec.ts", + "line": 7, + "column": 6, + "specs": [ + { + "title": "Allows the submission of feedback to the support email address", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "eccc8495635fa8823817-d22990fc313eb1664223", + "file": "feedback.spec.ts", + "line": 8, + "column": 7 + } + ] + } + ] + }, + { + "title": "forgot-password.spec.ts", + "file": "forgot-password.spec.ts", + "column": 0, + "line": 0, + "specs": [], + "suites": [ + { + "title": "Forgot Password Flow", + "file": "forgot-password.spec.ts", + "line": 8, + "column": 6, + "specs": [ + { + "title": "You can sign up with email, forget your password, ask to reset it, reset it, and sign in with the new password", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "852acb1e2419f9f859ad-7ca4ba4123f9edb9435f", + "file": "forgot-password.spec.ts", + "line": 9, + "column": 7 + } + ] + } + ] + }, + { + "title": "login.spec.ts", + "file": "login.spec.ts", + "column": 0, + "line": 0, + "specs": [], + "suites": [ + { + "title": "Login Flow", + "file": "login.spec.ts", + "line": 8, + "column": 6, + "specs": [ + { + "title": "When you are logged out and visit an authenticated path, you are redirected to the authenticated path after login", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "41d3fd2474a19feb00a1-730520150833593d6f71", + "file": "login.spec.ts", + "line": 9, + "column": 7 + }, + { + "title": "When you are logged out and visit an authenticated path, you are redirected to the authenticated path after signup", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "41d3fd2474a19feb00a1-58e4816d1573573303c6", + "file": "login.spec.ts", + "line": 32, + "column": 7 + } + ] + } + ] + }, + { + "title": "onboarding.spec.ts", + "file": "onboarding.spec.ts", + "column": 0, + "line": 0, + "specs": [], + "suites": [ + { + "title": "Onboarding Flow", + "file": "onboarding.spec.ts", + "line": 11, + "column": 6, + "specs": [ + { + "title": "You can sign up to captain with email, create an organization, invite someone to that organization, and then they can signup with the invite code and automatically be added to the organization.", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "fe534f0825407f213faa-23400fb099cd169aaa19", + "file": "onboarding.spec.ts", + "line": 60, + "column": 7 + }, + { + "title": "You can sign up to captain with email, create an organization, invite someone who already has an account to that organization, and then they can be deeplink logged in and added to the organization.", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "fe534f0825407f213faa-d3776a4870cf50739602", + "file": "onboarding.spec.ts", + "line": 76, + "column": 7 + }, + { + "title": "You can sign up for abq with email and create an organization.", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "fe534f0825407f213faa-46797fbc80c6cee73957", + "file": "onboarding.spec.ts", + "line": 95, + "column": 7 + } + ] + } + ] + }, + { + "title": "one-click-onboarding.spec.ts", + "file": "one-click-onboarding.spec.ts", + "column": 0, + "line": 0, + "specs": [], + "suites": [ + { + "title": "One Click Onboarding Flow", + "file": "one-click-onboarding.spec.ts", + "line": 5, + "column": 6, + "specs": [ + { + "title": "You can sign up with Github through the one click flow", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "a99fa0985f1dabea6453-538d9f010aa8e905fbe1", + "file": "one-click-onboarding.spec.ts", + "line": 6, + "column": 7 + }, + { + "title": "You can see the unsupported message and form for non-Github selections in the one click flow", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "a99fa0985f1dabea6453-bcda8c2f97d8014ffbce", + "file": "one-click-onboarding.spec.ts", + "line": 17, + "column": 7 + }, + { + "title": "You can see the unsupported message and form for not-listed selections in the one click flow", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "a99fa0985f1dabea6453-5c035f701518fc30a3cb", + "file": "one-click-onboarding.spec.ts", + "line": 40, + "column": 7 + } + ] + } + ] + }, + { + "title": "telemetry.spec.ts", + "file": "telemetry.spec.ts", + "column": 0, + "line": 0, + "specs": [], + "suites": [ + { + "title": "Telemetry functionality", + "file": "telemetry.spec.ts", + "line": 6, + "column": 6, + "specs": [ + { + "title": "Telemetry is enabled by default but can be opted out of", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "a7cf16ab6d0dd51a5bfe-4c41a79e04dfa8d335d6", + "file": "telemetry.spec.ts", + "line": 7, + "column": 7 + } + ] + } + ] + }, + { + "title": "time-zone.spec.ts", + "file": "time-zone.spec.ts", + "column": 0, + "line": 0, + "specs": [], + "suites": [ + { + "title": "Automatic time zone detection", + "file": "time-zone.spec.ts", + "line": 7, + "column": 6, + "specs": [ + { + "title": "Initially sets user's time zones to the browser's and lets them change it", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "14ec9e8289095a74f7c1-1bab191cfd2400997e5b", + "file": "time-zone.spec.ts", + "line": 8, + "column": 7 + } + ] + } + ] + } + ], + "errors": [ + { + "message": "expect(received).toHaveProperty(path)\n\nExpected path: \"pollingVal\"\nReceived path: []\n\nReceived value: \"Error while fetching Rails Config:\nSyntaxError: Unexpected token H in JSON at position 0\n at JSON.parse ()\n at APIResponse.json (/__w/captain/captain/node_modules/playwright-core/lib/client/fetch.js:238:17)\n at runNextTicks (node:internal/process/task_queues:61:5)\n at processImmediate (node:internal/timers:437:9)\n at _test.expect.poll.timeout (/__w/captain/captain/playwright/global-setup.ts:17:26)\n at /__w/captain/captain/node_modules/@playwright/test/lib/expect.js:179:19\n at TimeoutRunner.run (/__w/captain/captain/node_modules/playwright-core/lib/utils/timeoutRunner.js:46:14)\n at raceAgainstTimeout (/__w/captain/captain/node_modules/playwright-core/lib/utils/timeoutRunner.js:90:15)\n at pollAgainstTimeout (/__w/captain/captain/node_modules/playwright-core/lib/utils/timeoutRunner.js:110:38)\n at pollMatcher (/__w/captain/captain/node_modules/@playwright/test/lib/expect.js:178:18)\n at globalSetup (/__w/captain/captain/playwright/global-setup.ts:5:3)\n at /__w/captain/captain/node_modules/@playwright/test/lib/runner.js:484:11\n at Runner._runAndReportError (/__w/captain/captain/node_modules/@playwright/test/lib/runner.js:501:7)\n at Runner._performGlobalSetup (/__w/captain/captain/node_modules/@playwright/test/lib/runner.js:470:5)\n at Runner._run (/__w/captain/captain/node_modules/@playwright/test/lib/runner.js:380:28)\"\n\nCall Log:\n- Timeout 60000ms exceeded while waiting on the predicate", + "stack": "Error: expect(received).toHaveProperty(path)\n\nExpected path: \"pollingVal\"\nReceived path: []\n\nReceived value: \"Error while fetching Rails Config:\nSyntaxError: Unexpected token H in JSON at position 0\n at JSON.parse ()\n at APIResponse.json (/__w/captain/captain/node_modules/playwright-core/lib/client/fetch.js:238:17)\n at runNextTicks (node:internal/process/task_queues:61:5)\n at processImmediate (node:internal/timers:437:9)\n at _test.expect.poll.timeout (/__w/captain/captain/playwright/global-setup.ts:17:26)\n at /__w/captain/captain/node_modules/@playwright/test/lib/expect.js:179:19\n at TimeoutRunner.run (/__w/captain/captain/node_modules/playwright-core/lib/utils/timeoutRunner.js:46:14)\n at raceAgainstTimeout (/__w/captain/captain/node_modules/playwright-core/lib/utils/timeoutRunner.js:90:15)\n at pollAgainstTimeout (/__w/captain/captain/node_modules/playwright-core/lib/utils/timeoutRunner.js:110:38)\n at pollMatcher (/__w/captain/captain/node_modules/@playwright/test/lib/expect.js:178:18)\n at globalSetup (/__w/captain/captain/playwright/global-setup.ts:5:3)\n at /__w/captain/captain/node_modules/@playwright/test/lib/runner.js:484:11\n at Runner._runAndReportError (/__w/captain/captain/node_modules/@playwright/test/lib/runner.js:501:7)\n at Runner._performGlobalSetup (/__w/captain/captain/node_modules/@playwright/test/lib/runner.js:470:5)\n at Runner._run (/__w/captain/captain/node_modules/@playwright/test/lib/runner.js:380:28)\"\n\nCall Log:\n- Timeout 60000ms exceeded while waiting on the predicate\n at globalSetup (/__w/captain/captain/playwright/global-setup.ts:5:3)" + } + ] +} diff --git a/internal/test/fixtures/playwright_with_other_errors.json b/internal/test/fixtures/playwright_with_other_errors.json new file mode 100644 index 0000000..57d8469 --- /dev/null +++ b/internal/test/fixtures/playwright_with_other_errors.json @@ -0,0 +1,291 @@ +{ + "config": { + "forbidOnly": false, + "fullyParallel": true, + "globalSetup": null, + "globalTeardown": null, + "globalTimeout": 0, + "grep": {}, + "grepInvert": null, + "maxFailures": 0, + "metadata": {}, + "preserveOutput": "always", + "projects": [ + { + "outputDir": "/Users/kylekthompson/src/captain-examples/playwright/test-results", + "repeatEach": 1, + "retries": 2, + "id": "chromium", + "name": "chromium", + "testDir": "/Users/kylekthompson/src/captain-examples/playwright/tests", + "testIgnore": [], + "testMatch": [ + "**/?(*.)@(spec|test).*" + ], + "timeout": 5000 + }, + { + "outputDir": "/Users/kylekthompson/src/captain-examples/playwright/test-results", + "repeatEach": 1, + "retries": 2, + "id": "firefox", + "name": "firefox", + "testDir": "/Users/kylekthompson/src/captain-examples/playwright/tests", + "testIgnore": [], + "testMatch": [ + "**/?(*.)@(spec|test).*" + ], + "timeout": 5000 + }, + { + "outputDir": "/Users/kylekthompson/src/captain-examples/playwright/test-results", + "repeatEach": 1, + "retries": 2, + "id": "webkit", + "name": "webkit", + "testDir": "/Users/kylekthompson/src/captain-examples/playwright/tests", + "testIgnore": [], + "testMatch": [ + "**/?(*.)@(spec|test).*" + ], + "timeout": 5000 + }, + { + "outputDir": "/Users/kylekthompson/src/captain-examples/playwright/test-results", + "repeatEach": 1, + "retries": 2, + "id": "Mobile Chrome", + "name": "Mobile Chrome", + "testDir": "/Users/kylekthompson/src/captain-examples/playwright/tests", + "testIgnore": [], + "testMatch": [ + "**/?(*.)@(spec|test).*" + ], + "timeout": 5000 + }, + { + "outputDir": "/Users/kylekthompson/src/captain-examples/playwright/test-results", + "repeatEach": 1, + "retries": 2, + "id": "Mobile Safari", + "name": "Mobile Safari", + "testDir": "/Users/kylekthompson/src/captain-examples/playwright/tests", + "testIgnore": [], + "testMatch": [ + "**/?(*.)@(spec|test).*" + ], + "timeout": 5000 + }, + { + "outputDir": "/Users/kylekthompson/src/captain-examples/playwright/test-results", + "repeatEach": 1, + "retries": 2, + "id": "Microsoft Edge", + "name": "Microsoft Edge", + "testDir": "/Users/kylekthompson/src/captain-examples/playwright/tests", + "testIgnore": [], + "testMatch": [ + "**/?(*.)@(spec|test).*" + ], + "timeout": 5000 + }, + { + "outputDir": "/Users/kylekthompson/src/captain-examples/playwright/test-results", + "repeatEach": 1, + "retries": 2, + "id": "Google Chrome", + "name": "Google Chrome", + "testDir": "/Users/kylekthompson/src/captain-examples/playwright/tests", + "testIgnore": [], + "testMatch": [ + "**/?(*.)@(spec|test).*" + ], + "timeout": 5000 + } + ], + "reporter": [ + [ + "html", + null + ], + [ + "list", + null + ], + [ + "json", + { + "outputFile": "test-results.json" + } + ] + ], + "reportSlowTests": { + "max": 5, + "threshold": 15000 + }, + "configFile": "/Users/kylekthompson/src/captain-examples/playwright/playwright.config.ts", + "rootDir": "/Users/kylekthompson/src/captain-examples/playwright/tests", + "quiet": false, + "shard": null, + "updateSnapshots": "missing", + "version": "1.28.1", + "workers": 5, + "webServer": null + }, + "suites": [ + { + "title": "error.spec.ts", + "file": "error.spec.ts", + "column": 0, + "line": 0, + "specs": [ + { + "title": "homepage has title and links to intro page", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "chromium", + "projectName": "chromium", + "results": [], + "status": "skipped" + } + ], + "id": "4fd29ac3310d82f3bf5a-385805a0b9b707ec90b9", + "file": "error.spec.ts", + "line": 3, + "column": 1 + }, + { + "title": "homepage has title and links to intro page", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "firefox", + "projectName": "firefox", + "results": [], + "status": "skipped" + } + ], + "id": "4fd29ac3310d82f3bf5a-7105e51c68fe510fe2c6", + "file": "error.spec.ts", + "line": 3, + "column": 1 + }, + { + "title": "homepage has title and links to intro page", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "webkit", + "projectName": "webkit", + "results": [], + "status": "skipped" + } + ], + "id": "4fd29ac3310d82f3bf5a-4c78831f5bf257fea641", + "file": "error.spec.ts", + "line": 3, + "column": 1 + }, + { + "title": "homepage has title and links to intro page", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "Mobile Chrome", + "projectName": "Mobile Chrome", + "results": [], + "status": "skipped" + } + ], + "id": "4fd29ac3310d82f3bf5a-8053b5143eb263a12c8b", + "file": "error.spec.ts", + "line": 3, + "column": 1 + }, + { + "title": "homepage has title and links to intro page", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "Mobile Safari", + "projectName": "Mobile Safari", + "results": [], + "status": "skipped" + } + ], + "id": "4fd29ac3310d82f3bf5a-234ffbadaafb44f680a5", + "file": "error.spec.ts", + "line": 3, + "column": 1 + }, + { + "title": "homepage has title and links to intro page", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "Microsoft Edge", + "projectName": "Microsoft Edge", + "results": [], + "status": "skipped" + } + ], + "id": "4fd29ac3310d82f3bf5a-ecf2d56d349d38de7035", + "file": "error.spec.ts", + "line": 3, + "column": 1 + }, + { + "title": "homepage has title and links to intro page", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 0, + "annotations": [], + "expectedStatus": "passed", + "projectId": "Google Chrome", + "projectName": "Google Chrome", + "results": [], + "status": "skipped" + } + ], + "id": "4fd29ac3310d82f3bf5a-85490c817b7139c58916", + "file": "error.spec.ts", + "line": 3, + "column": 1 + } + ] + } + ], + "errors": [ + { + "message": "it broke", + "stack": "Error: it broke\n at Object. (/Users/kylekthompson/src/captain-examples/playwright/tests/error.spec.ts:22:7)" + } + ] +} diff --git a/internal/test/fixtures/pytest_reportlog.jsonl b/internal/test/fixtures/pytest_reportlog.jsonl new file mode 100644 index 0000000..2b73cda --- /dev/null +++ b/internal/test/fixtures/pytest_reportlog.jsonl @@ -0,0 +1,65 @@ +{"pytest_version": "7.2.0", "$report_type": "SessionStart"} +{"nodeid": "test_top_level.py::test_top_level_passing", "location": ["test_top_level.py", 0, "test_top_level_passing"], "keywords": {"test_top_level_passing": 1, "test_top_level.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00016975001199170947, "$report_type": "TestReport", "item_index": 0, "worker_id": "gw0", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "test_top_level.py::test_top_level_passing", "location": ["test_top_level.py", 0, "test_top_level_passing"], "keywords": {"test_top_level_passing": 1, "test_top_level.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [], "sections": [], "duration": 9.391701314598322e-05, "$report_type": "TestReport", "item_index": 0, "worker_id": "gw0", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "test_top_level.py::test_top_level_passing", "location": ["test_top_level.py", 0, "test_top_level_passing"], "keywords": {"test_top_level_passing": 1, "test_top_level.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 6.833299994468689e-05, "$report_type": "TestReport", "item_index": 0, "worker_id": "gw0", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_nested_passing", "location": ["nested/test_nested.py", 30, "test_nested_passing"], "keywords": {"test_nested_passing": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.0001656670356169343, "$report_type": "TestReport", "item_index": 8, "worker_id": "gw4", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_nested_passing", "location": ["nested/test_nested.py", 30, "test_nested_passing"], "keywords": {"test_nested_passing": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [], "sections": [], "duration": 8.32080258987844e-05, "$report_type": "TestReport", "item_index": 8, "worker_id": "gw4", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_nested_passing", "location": ["nested/test_nested.py", 30, "test_nested_passing"], "keywords": {"test_nested_passing": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 5.9166981372982264e-05, "$report_type": "TestReport", "item_index": 8, "worker_id": "gw4", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "test_top_level.py::test_one", "location": ["test_top_level.py", 6, "test_one"], "keywords": {"test_one": 1, "test_top_level.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.0001701659639365971, "$report_type": "TestReport", "item_index": 2, "worker_id": "gw1", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "test_top_level.py::test_one", "location": ["test_top_level.py", 6, "test_one"], "keywords": {"test_one": 1, "test_top_level.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [], "sections": [], "duration": 8.183397585526109e-05, "$report_type": "TestReport", "item_index": 2, "worker_id": "gw1", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "test_top_level.py::test_one", "location": ["test_top_level.py", 6, "test_one"], "keywords": {"test_one": 1, "test_top_level.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 6.216700421646237e-05, "$report_type": "TestReport", "item_index": 2, "worker_id": "gw1", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::TestSomeClassFailingTeardown::test_some_method", "location": ["nested/test_nested.py", 23, "TestSomeClassFailingTeardown.test_some_method"], "keywords": {"test_some_method": 1, "TestSomeClassFailingTeardown": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00026175001403316855, "$report_type": "TestReport", "item_index": 6, "worker_id": "gw3", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::TestSomeClassFailingTeardown::test_some_method", "location": ["nested/test_nested.py", 23, "TestSomeClassFailingTeardown.test_some_method"], "keywords": {"test_some_method": 1, "TestSomeClassFailingTeardown": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [], "sections": [], "duration": 9.099999442696571e-05, "$report_type": "TestReport", "item_index": 6, "worker_id": "gw3", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_skipped_no_reason", "location": ["nested/test_nested.py", 50, "test_skipped_no_reason"], "keywords": {"test_skipped_no_reason": 1, "skip": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "skipped", "longrepr": ["/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", 51, "Skipped: unconditional skip"], "when": "setup", "user_properties": [], "sections": [], "duration": 0.00015966600039973855, "$report_type": "TestReport", "item_index": 14, "worker_id": "gw7", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_skipped_no_reason", "location": ["nested/test_nested.py", 50, "test_skipped_no_reason"], "keywords": {"test_skipped_no_reason": 1, "skip": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.00010349997319281101, "$report_type": "TestReport", "item_index": 14, "worker_id": "gw7", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_parameterized[6*9-42]", "location": ["nested/test_nested.py", 58, "test_parameterized[6*9-42]"], "keywords": {"test_parameterized[6*9-42]": 1, "parametrize": 1, "pytestmark": 1, "6*9-42": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.0003403750015422702, "$report_type": "TestReport", "item_index": 18, "worker_id": "gw9", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_one", "location": ["nested/test_nested.py", 36, "test_one"], "keywords": {"test_one": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.0001560840173624456, "$report_type": "TestReport", "item_index": 10, "worker_id": "gw5", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_one", "location": ["nested/test_nested.py", 36, "test_one"], "keywords": {"test_one": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [], "sections": [], "duration": 8.545798482373357e-05, "$report_type": "TestReport", "item_index": 10, "worker_id": "gw5", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_one", "location": ["nested/test_nested.py", 36, "test_one"], "keywords": {"test_one": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 6.925000343471766e-05, "$report_type": "TestReport", "item_index": 10, "worker_id": "gw5", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_parameterized[3+5-8]", "location": ["nested/test_nested.py", 58, "test_parameterized[3+5-8]"], "keywords": {"test_parameterized[3+5-8]": 1, "parametrize": 1, "pytestmark": 1, "3+5-8": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.0003144579823128879, "$report_type": "TestReport", "item_index": 16, "worker_id": "gw8", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_parameterized[3+5-8]", "location": ["nested/test_nested.py", 58, "test_parameterized[3+5-8]"], "keywords": {"test_parameterized[3+5-8]": 1, "parametrize": 1, "pytestmark": 1, "3+5-8": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [["test_input", "3+5"], ["expected", 8], ["arbitrary", "value"]], "sections": [], "duration": 0.0001825410290621221, "$report_type": "TestReport", "item_index": 16, "worker_id": "gw8", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_parameterized[3+5-8]", "location": ["nested/test_nested.py", 58, "test_parameterized[3+5-8]"], "keywords": {"test_parameterized[3+5-8]": 1, "parametrize": 1, "pytestmark": 1, "3+5-8": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [["test_input", "3+5"], ["expected", 8], ["arbitrary", "value"]], "sections": [], "duration": 8.999998681247234e-05, "$report_type": "TestReport", "item_index": 16, "worker_id": "gw8", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_exception", "location": ["nested/test_nested.py", 43, "test_exception"], "keywords": {"test_exception": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00016704201698303223, "$report_type": "TestReport", "item_index": 12, "worker_id": "gw6", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "test_top_level.py::test_top_level_failing", "location": ["test_top_level.py", 3, "test_top_level_failing"], "keywords": {"test_top_level_failing": 1, "test_top_level.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00010533299064263701, "$report_type": "TestReport", "item_index": 1, "worker_id": "gw0", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_nested_failing", "location": ["nested/test_nested.py", 33, "test_nested_failing"], "keywords": {"test_nested_failing": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 9.962497279047966e-05, "$report_type": "TestReport", "item_index": 9, "worker_id": "gw4", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_slow", "location": ["nested/test_nested.py", 39, "test_slow"], "keywords": {"test_slow": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 8.62079905346036e-05, "$report_type": "TestReport", "item_index": 11, "worker_id": "gw5", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_skipped_conditionally", "location": ["nested/test_nested.py", 54, "test_skipped_conditionally"], "keywords": {"test_skipped_conditionally": 1, "skipif": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "skipped", "longrepr": ["/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", 55, "Skipped: does not run on mac"], "when": "setup", "user_properties": [], "sections": [], "duration": 9.225000394508243e-05, "$report_type": "TestReport", "item_index": 15, "worker_id": "gw7", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_skipped_conditionally", "location": ["nested/test_nested.py", 54, "test_skipped_conditionally"], "keywords": {"test_skipped_conditionally": 1, "skipif": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 6.5833970438689e-05, "$report_type": "TestReport", "item_index": 15, "worker_id": "gw7", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::TestSomeClass::test_some_method", "location": ["nested/test_nested.py", 5, "TestSomeClass.test_some_method"], "keywords": {"test_some_method": 1, "TestSomeClass": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.0001015419838950038, "$report_type": "TestReport", "item_index": 3, "worker_id": "gw1", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_parameterized[2+4-6]", "location": ["nested/test_nested.py", 58, "test_parameterized[2+4-6]"], "keywords": {"test_parameterized[2+4-6]": 1, "parametrize": 1, "pytestmark": 1, "2+4-6": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00020729098469018936, "$report_type": "TestReport", "item_index": 17, "worker_id": "gw8", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_parameterized[2+4-6]", "location": ["nested/test_nested.py", 58, "test_parameterized[2+4-6]"], "keywords": {"test_parameterized[2+4-6]": 1, "parametrize": 1, "pytestmark": 1, "2+4-6": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [["test_input", "2+4"], ["expected", 6], ["arbitrary", "value"]], "sections": [], "duration": 9.799998952075839e-05, "$report_type": "TestReport", "item_index": 17, "worker_id": "gw8", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_parameterized[2+4-6]", "location": ["nested/test_nested.py", 58, "test_parameterized[2+4-6]"], "keywords": {"test_parameterized[2+4-6]": 1, "parametrize": 1, "pytestmark": 1, "2+4-6": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [["test_input", "2+4"], ["expected", 6], ["arbitrary", "value"]], "sections": [], "duration": 8.68329661898315e-05, "$report_type": "TestReport", "item_index": 17, "worker_id": "gw8", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::TestSomeClassFailingSetup::test_some_method", "location": ["nested/test_nested.py", 12, "TestSomeClassFailingSetup.test_some_method"], "keywords": {"test_some_method": 1, "TestSomeClassFailingSetup": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "failed", "longrepr": {"reprcrash": {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 11, "message": "assert True == False"}, "reprtraceback": {"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def setup_method(self, method):", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": [["self", ""], ["method", ">"]]}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 11, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, "sections": [], "chain": [[{"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def setup_method(self, method):", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": [["self", ""], ["method", ">"]]}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 11, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 11, "message": "assert True == False"}, null]]}, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00033820903627201915, "$report_type": "TestReport", "item_index": 4, "worker_id": "gw2", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::TestSomeClassFailingSetup::test_some_method", "location": ["nested/test_nested.py", 12, "TestSomeClassFailingSetup.test_some_method"], "keywords": {"test_some_method": 1, "TestSomeClassFailingSetup": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.00013279198901727796, "$report_type": "TestReport", "item_index": 4, "worker_id": "gw2", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_exception", "location": ["nested/test_nested.py", 43, "test_exception"], "keywords": {"test_exception": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "failed", "longrepr": {"reprcrash": {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 45, "message": "Exception: Uh oh"}, "reprtraceback": {"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_exception():", "> raise Exception(\"Uh oh\")", "E Exception: Uh oh"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 45, "message": "Exception"}, "style": "long"}}], "extraline": null, "style": "long"}, "sections": [], "chain": [[{"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_exception():", "> raise Exception(\"Uh oh\")", "E Exception: Uh oh"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 45, "message": "Exception"}, "style": "long"}}], "extraline": null, "style": "long"}, {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 45, "message": "Exception: Uh oh"}, null]]}, "when": "call", "user_properties": [], "sections": [], "duration": 9.612500434741378e-05, "$report_type": "TestReport", "item_index": 12, "worker_id": "gw6", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_exception", "location": ["nested/test_nested.py", 43, "test_exception"], "keywords": {"test_exception": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.00010900001507252455, "$report_type": "TestReport", "item_index": 12, "worker_id": "gw6", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::TestSomeClassFailingTeardown::test_some_method", "location": ["nested/test_nested.py", 23, "TestSomeClassFailingTeardown.test_some_method"], "keywords": {"test_some_method": 1, "TestSomeClassFailingTeardown": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "failed", "longrepr": {"reprcrash": {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 22, "message": "assert True == False"}, "reprtraceback": {"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def teardown_method(self, method):", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": [["self", ""], ["method", ">"]]}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 22, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, "sections": [], "chain": [[{"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def teardown_method(self, method):", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": [["self", ""], ["method", ">"]]}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 22, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 22, "message": "assert True == False"}, null]]}, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.00014875002671033144, "$report_type": "TestReport", "item_index": 6, "worker_id": "gw3", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_parameterized[6*9-42]", "location": ["nested/test_nested.py", 58, "test_parameterized[6*9-42]"], "keywords": {"test_parameterized[6*9-42]": 1, "parametrize": 1, "pytestmark": 1, "6*9-42": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "failed", "longrepr": {"reprcrash": {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 64, "message": "AssertionError: assert 54 == 42\n + where 54 = eval('6*9')"}, "reprtraceback": {"reprentries": [{"type": "ReprEntry", "data": {"lines": [" @pytest.mark.parametrize(\"test_input,expected\", [(\"3+5\", 8), (\"2+4\", 6), (\"6*9\", 42)])", " def test_parameterized(record_property, test_input, expected):", " record_property(\"test_input\", test_input)", " record_property(\"expected\", expected)", " record_property(\"arbitrary\", \"value\")", "> assert eval(test_input) == expected", "E AssertionError: assert 54 == 42", "E + where 54 = eval('6*9')"], "reprfuncargs": {"args": [["record_property", ".append_property at 0x104eaec20>"], ["test_input", "'6*9'"], ["expected", "42"]]}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 64, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, "sections": [], "chain": [[{"reprentries": [{"type": "ReprEntry", "data": {"lines": [" @pytest.mark.parametrize(\"test_input,expected\", [(\"3+5\", 8), (\"2+4\", 6), (\"6*9\", 42)])", " def test_parameterized(record_property, test_input, expected):", " record_property(\"test_input\", test_input)", " record_property(\"expected\", expected)", " record_property(\"arbitrary\", \"value\")", "> assert eval(test_input) == expected", "E AssertionError: assert 54 == 42", "E + where 54 = eval('6*9')"], "reprfuncargs": {"args": [["record_property", ".append_property at 0x104eaec20>"], ["test_input", "'6*9'"], ["expected", "42"]]}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 64, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 64, "message": "AssertionError: assert 54 == 42\n + where 54 = eval('6*9')"}, null]]}, "when": "call", "user_properties": [["test_input", "6*9"], ["expected", 42], ["arbitrary", "value"]], "sections": [], "duration": 0.00018937501590698957, "$report_type": "TestReport", "item_index": 18, "worker_id": "gw9", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_parameterized[6*9-42]", "location": ["nested/test_nested.py", 58, "test_parameterized[6*9-42]"], "keywords": {"test_parameterized[6*9-42]": 1, "parametrize": 1, "pytestmark": 1, "6*9-42": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [["test_input", "6*9"], ["expected", 42], ["arbitrary", "value"]], "sections": [], "duration": 0.00013695901725441217, "$report_type": "TestReport", "item_index": 18, "worker_id": "gw9", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::TestSomeClassFailingSetup::test_some_skipped_method", "location": ["nested/test_nested.py", 15, "TestSomeClassFailingSetup.test_some_skipped_method"], "keywords": {"test_some_skipped_method": 1, "skip": 1, "pytestmark": 1, "TestSomeClassFailingSetup": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "skipped", "longrepr": ["/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", 16, "Skipped: to test skipping a test"], "when": "setup", "user_properties": [], "sections": [], "duration": 8.02910071797669e-05, "$report_type": "TestReport", "item_index": 5, "worker_id": "gw2", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_skipped", "location": ["nested/test_nested.py", 46, "test_skipped"], "keywords": {"test_skipped": 1, "skip": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "skipped", "longrepr": ["/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", 47, "Skipped: to test skipping a test"], "when": "setup", "user_properties": [], "sections": [], "duration": 0.00010070798452943563, "$report_type": "TestReport", "item_index": 13, "worker_id": "gw6", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::TestSomeClassFailingSetup::test_some_skipped_method", "location": ["nested/test_nested.py", 15, "TestSomeClassFailingSetup.test_some_skipped_method"], "keywords": {"test_some_skipped_method": 1, "skip": 1, "pytestmark": 1, "TestSomeClassFailingSetup": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 6.425002356991172e-05, "$report_type": "TestReport", "item_index": 5, "worker_id": "gw2", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "test_top_level.py::test_top_level_failing", "location": ["test_top_level.py", 3, "test_top_level_failing"], "keywords": {"test_top_level_failing": 1, "test_top_level.py": 1, "pytest": 1}, "outcome": "failed", "longrepr": {"reprcrash": {"path": "/Users/kylekthompson/src/captain-examples/pytest/test_top_level.py", "lineno": 5, "message": "assert True == False"}, "reprtraceback": {"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_top_level_failing():", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "test_top_level.py", "lineno": 5, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, "sections": [], "chain": [[{"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_top_level_failing():", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "test_top_level.py", "lineno": 5, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, {"path": "/Users/kylekthompson/src/captain-examples/pytest/test_top_level.py", "lineno": 5, "message": "assert True == False"}, null]]}, "when": "call", "user_properties": [], "sections": [], "duration": 0.00014633301179856062, "$report_type": "TestReport", "item_index": 1, "worker_id": "gw0", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "test_top_level.py::test_top_level_failing", "location": ["test_top_level.py", 3, "test_top_level_failing"], "keywords": {"test_top_level_failing": 1, "test_top_level.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.00010641600238159299, "$report_type": "TestReport", "item_index": 1, "worker_id": "gw0", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_skipped", "location": ["nested/test_nested.py", 46, "test_skipped"], "keywords": {"test_skipped": 1, "skip": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 6.470800144597888e-05, "$report_type": "TestReport", "item_index": 13, "worker_id": "gw6", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::TestSomeClassFailingTeardown::test_some_skipped_method", "location": ["nested/test_nested.py", 26, "TestSomeClassFailingTeardown.test_some_skipped_method"], "keywords": {"test_some_skipped_method": 1, "skip": 1, "pytestmark": 1, "TestSomeClassFailingTeardown": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "skipped", "longrepr": ["/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", 27, "Skipped: to test skipping a test"], "when": "setup", "user_properties": [], "sections": [], "duration": 0.00011629099026322365, "$report_type": "TestReport", "item_index": 7, "worker_id": "gw3", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::TestSomeClassFailingTeardown::test_some_skipped_method", "location": ["nested/test_nested.py", 26, "TestSomeClassFailingTeardown.test_some_skipped_method"], "keywords": {"test_some_skipped_method": 1, "skip": 1, "pytestmark": 1, "TestSomeClassFailingTeardown": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 7.237499812617898e-05, "$report_type": "TestReport", "item_index": 7, "worker_id": "gw3", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_expected_fail_failing", "location": ["nested/test_nested.py", 65, "test_expected_fail_failing"], "keywords": {"test_expected_fail_failing": 1, "xfail": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00011212495155632496, "$report_type": "TestReport", "item_index": 19, "worker_id": "gw9", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_nested_failing", "location": ["nested/test_nested.py", 33, "test_nested_failing"], "keywords": {"test_nested_failing": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "failed", "longrepr": {"reprcrash": {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 35, "message": "assert True == False"}, "reprtraceback": {"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_nested_failing():", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 35, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, "sections": [], "chain": [[{"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_nested_failing():", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 35, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 35, "message": "assert True == False"}, null]]}, "when": "call", "user_properties": [], "sections": [], "duration": 0.00013475003652274609, "$report_type": "TestReport", "item_index": 9, "worker_id": "gw4", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_nested_failing", "location": ["nested/test_nested.py", 33, "test_nested_failing"], "keywords": {"test_nested_failing": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.0001103340182453394, "$report_type": "TestReport", "item_index": 9, "worker_id": "gw4", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::TestSomeClass::test_some_method", "location": ["nested/test_nested.py", 5, "TestSomeClass.test_some_method"], "keywords": {"test_some_method": 1, "TestSomeClass": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "failed", "longrepr": {"reprcrash": {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 7, "message": "assert True == False"}, "reprtraceback": {"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_some_method(self):", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": [["self", ""]]}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 7, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, "sections": [], "chain": [[{"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_some_method(self):", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": [["self", ""]]}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 7, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 7, "message": "assert True == False"}, null]]}, "when": "call", "user_properties": [], "sections": [], "duration": 0.0001479579950682819, "$report_type": "TestReport", "item_index": 3, "worker_id": "gw1", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::TestSomeClass::test_some_method", "location": ["nested/test_nested.py", 5, "TestSomeClass.test_some_method"], "keywords": {"test_some_method": 1, "TestSomeClass": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.0001034580054692924, "$report_type": "TestReport", "item_index": 3, "worker_id": "gw1", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_expected_fail_passing", "location": ["nested/test_nested.py", 69, "test_expected_fail_passing"], "keywords": {"test_expected_fail_passing": 1, "xfail": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.0001040829811245203, "$report_type": "TestReport", "item_index": 20, "worker_id": "gw0", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_expected_fail_failing", "location": ["nested/test_nested.py", 65, "test_expected_fail_failing"], "keywords": {"test_expected_fail_failing": 1, "xfail": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "skipped", "longrepr": {"reprcrash": {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 68, "message": "assert True == False"}, "reprtraceback": {"reprentries": [{"type": "ReprEntry", "data": {"lines": [" @pytest.mark.xfail", " def test_expected_fail_failing():", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 68, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, "sections": [], "chain": [[{"reprentries": [{"type": "ReprEntry", "data": {"lines": [" @pytest.mark.xfail", " def test_expected_fail_failing():", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 68, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 68, "message": "assert True == False"}, null]]}, "when": "call", "user_properties": [], "sections": [], "duration": 0.00012441701255738735, "wasxfail": "", "$report_type": "TestReport", "item_index": 19, "worker_id": "gw9", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_expected_fail_passing", "location": ["nested/test_nested.py", 69, "test_expected_fail_passing"], "keywords": {"test_expected_fail_passing": 1, "xfail": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [], "sections": [], "duration": 6.53750030323863e-05, "wasxfail": "", "$report_type": "TestReport", "item_index": 20, "worker_id": "gw0", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_expected_fail_passing", "location": ["nested/test_nested.py", 69, "test_expected_fail_passing"], "keywords": {"test_expected_fail_passing": 1, "xfail": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 5.395803600549698e-05, "$report_type": "TestReport", "item_index": 20, "worker_id": "gw0", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_expected_fail_failing", "location": ["nested/test_nested.py", 65, "test_expected_fail_failing"], "keywords": {"test_expected_fail_failing": 1, "xfail": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 7.108302088454366e-05, "$report_type": "TestReport", "item_index": 19, "worker_id": "gw9", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_strict_expected_fail_failing", "location": ["nested/test_nested.py", 73, "test_strict_expected_fail_failing"], "keywords": {"test_strict_expected_fail_failing": 1, "xfail": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 9.012501686811447e-05, "$report_type": "TestReport", "item_index": 21, "worker_id": "gw4", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_strict_expected_fail_passing", "location": ["nested/test_nested.py", 77, "test_strict_expected_fail_passing"], "keywords": {"test_strict_expected_fail_passing": 1, "xfail": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00010608398588374257, "$report_type": "TestReport", "item_index": 22, "worker_id": "gw1", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_strict_expected_fail_passing", "location": ["nested/test_nested.py", 77, "test_strict_expected_fail_passing"], "keywords": {"test_strict_expected_fail_passing": 1, "xfail": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "failed", "longrepr": "[XPASS(strict)] ", "when": "call", "user_properties": [], "sections": [], "duration": 0.00012395897647365928, "$report_type": "TestReport", "item_index": 22, "worker_id": "gw1", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_strict_expected_fail_passing", "location": ["nested/test_nested.py", 77, "test_strict_expected_fail_passing"], "keywords": {"test_strict_expected_fail_passing": 1, "xfail": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.00011654099216684699, "$report_type": "TestReport", "item_index": 22, "worker_id": "gw1", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_strict_expected_fail_failing", "location": ["nested/test_nested.py", 73, "test_strict_expected_fail_failing"], "keywords": {"test_strict_expected_fail_failing": 1, "xfail": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "skipped", "longrepr": {"reprcrash": {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 76, "message": "assert True == False"}, "reprtraceback": {"reprentries": [{"type": "ReprEntry", "data": {"lines": [" @pytest.mark.xfail(strict=True)", " def test_strict_expected_fail_failing():", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 76, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, "sections": [], "chain": [[{"reprentries": [{"type": "ReprEntry", "data": {"lines": [" @pytest.mark.xfail(strict=True)", " def test_strict_expected_fail_failing():", "> assert True == False", "E assert True == False"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "nested/test_nested.py", "lineno": 76, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, {"path": "/Users/kylekthompson/src/captain-examples/pytest/nested/test_nested.py", "lineno": 76, "message": "assert True == False"}, null]]}, "when": "call", "user_properties": [], "sections": [], "duration": 0.00011691701365634799, "wasxfail": "", "$report_type": "TestReport", "item_index": 21, "worker_id": "gw4", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_strict_expected_fail_failing", "location": ["nested/test_nested.py", 73, "test_strict_expected_fail_failing"], "keywords": {"test_strict_expected_fail_failing": 1, "xfail": 1, "pytestmark": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 9.187497198581696e-05, "$report_type": "TestReport", "item_index": 21, "worker_id": "gw4", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_slow", "location": ["nested/test_nested.py", 39, "test_slow"], "keywords": {"test_slow": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [], "sections": [], "duration": 1.5055242919479497, "$report_type": "TestReport", "item_index": 11, "worker_id": "gw5", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"nodeid": "nested/test_nested.py::test_slow", "location": ["nested/test_nested.py", 39, "test_slow"], "keywords": {"test_slow": 1, "nested/test_nested.py": 1, "pytest": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.00023850001161918044, "$report_type": "TestReport", "item_index": 11, "worker_id": "gw5", "testrun_uid": "1f1c8d3923744ae880c6431a78d94d26", "node": ""} +{"exitstatus": 1, "$report_type": "SessionFinish"} diff --git a/internal/test/fixtures/rspec.json b/internal/test/fixtures/rspec.json new file mode 100644 index 0000000..bfe8c29 --- /dev/null +++ b/internal/test/fixtures/rspec.json @@ -0,0 +1,2340 @@ +{ + "version": "3.11.0", + "examples": [ + { + "id": "./spec/examples/class_spec.rb[1:1]", + "description": "has top-level passing tests", + "full_description": "Tests::Case has top-level passing tests", + "status": "passed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 5, + "run_time": 0.030795, + "pending_message": null + }, + { + "id": "./spec/examples/class_spec.rb[1:2]", + "description": "has top-level failing tests", + "full_description": "Tests::Case has top-level failing tests", + "status": "failed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 9, + "run_time": 0.006114, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/class_spec.rb:10:in `block (2 levels) in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:3]", + "description": "has top-level aggregated failing tests", + "full_description": "Tests::Case has top-level aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 13, + "run_time": 0.003131, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:14:in `block (2 levels) in \u003ctop (required)\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:15:in `block (2 levels) in \u003ctop (required)\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:4]", + "description": "has top-level skipped tests", + "full_description": "Tests::Case has top-level skipped tests", + "status": "pending", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 18, + "run_time": 6.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/class_spec.rb[1:5]", + "description": "has top-level passing pended tests", + "full_description": "Tests::Case has top-level passing pended tests", + "status": "failed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 23, + "run_time": 0.003167, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/class_spec.rb:23"] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:6]", + "description": "has top-level failing pended tests", + "full_description": "Tests::Case has top-level failing pended tests", + "status": "pending", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 28, + "run_time": 0.002972, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/class_spec.rb[1:7:1]", + "description": "has top-level passing tests", + "full_description": "Tests::Case behaves like shared examples has top-level passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 2, + "run_time": 0.002982, + "pending_message": null + }, + { + "id": "./spec/examples/class_spec.rb[1:7:2]", + "description": "has top-level failing tests", + "full_description": "Tests::Case behaves like shared examples has top-level failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 6, + "run_time": 0.002971, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:3]", + "description": "has top-level aggregated failing tests", + "full_description": "Tests::Case behaves like shared examples has top-level aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 10, + "run_time": 0.00254, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:4]", + "description": "has top-level skipped tests", + "full_description": "Tests::Case behaves like shared examples has top-level skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 15, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/class_spec.rb[1:7:5]", + "description": "has top-level passing pended tests", + "full_description": "Tests::Case behaves like shared examples has top-level passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 20, + "run_time": 0.003049, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:20"] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:6]", + "description": "has top-level failing pended tests", + "full_description": "Tests::Case behaves like shared examples has top-level failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 25, + "run_time": 0.003055, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:1]", + "description": "has passing tests", + "full_description": "Tests::Case behaves like shared examples within a context has passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 31, + "run_time": 0.002859, + "pending_message": null + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:2]", + "description": "has failing tests", + "full_description": "Tests::Case behaves like shared examples within a context has failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 35, + "run_time": 0.002979, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:3]", + "description": "has aggregated failing tests", + "full_description": "Tests::Case behaves like shared examples within a context has aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 39, + "run_time": 0.002438, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:4]", + "description": "has skipped tests", + "full_description": "Tests::Case behaves like shared examples within a context has skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 44, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:5]", + "description": "has passing pended tests", + "full_description": "Tests::Case behaves like shared examples within a context has passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 49, + "run_time": 0.002583, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:49"] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:7:7:6]", + "description": "has failing pended tests", + "full_description": "Tests::Case behaves like shared examples within a context has failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 54, + "run_time": 0.003151, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/class_spec.rb[1:8:1]", + "description": "has passing tests", + "full_description": "Tests::Case within a context has passing tests", + "status": "passed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 36, + "run_time": 0.002502, + "pending_message": null + }, + { + "id": "./spec/examples/class_spec.rb[1:8:2]", + "description": "has failing tests", + "full_description": "Tests::Case within a context has failing tests", + "status": "failed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 40, + "run_time": 0.00306, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/class_spec.rb:41:in `block (3 levels) in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:3]", + "description": "has aggregated failing tests", + "full_description": "Tests::Case within a context has aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 44, + "run_time": 0.003055, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:45:in `block (3 levels) in \u003ctop (required)\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/class_spec.rb:46:in `block (3 levels) in \u003ctop (required)\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:4]", + "description": "has skipped tests", + "full_description": "Tests::Case within a context has skipped tests", + "status": "pending", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 49, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/class_spec.rb[1:8:5]", + "description": "has passing pended tests", + "full_description": "Tests::Case within a context has passing pended tests", + "status": "failed", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 54, + "run_time": 0.002014, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/class_spec.rb:54"] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:6]", + "description": "has failing pended tests", + "full_description": "Tests::Case within a context has failing pended tests", + "status": "pending", + "file_path": "./spec/examples/class_spec.rb", + "line_number": 59, + "run_time": 0.002398, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:1]", + "description": "has top-level passing tests", + "full_description": "Tests::Case within a context behaves like shared examples has top-level passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 2, + "run_time": 0.002994, + "pending_message": null + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:2]", + "description": "has top-level failing tests", + "full_description": "Tests::Case within a context behaves like shared examples has top-level failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 6, + "run_time": 0.002811, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:3]", + "description": "has top-level aggregated failing tests", + "full_description": "Tests::Case within a context behaves like shared examples has top-level aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 10, + "run_time": 0.002654, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:4]", + "description": "has top-level skipped tests", + "full_description": "Tests::Case within a context behaves like shared examples has top-level skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 15, + "run_time": 3.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:5]", + "description": "has top-level passing pended tests", + "full_description": "Tests::Case within a context behaves like shared examples has top-level passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 20, + "run_time": 0.003018, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:20"] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:6]", + "description": "has top-level failing pended tests", + "full_description": "Tests::Case within a context behaves like shared examples has top-level failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 25, + "run_time": 0.002221, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:1]", + "description": "has passing tests", + "full_description": "Tests::Case within a context behaves like shared examples within a context has passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 31, + "run_time": 0.002212, + "pending_message": null + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:2]", + "description": "has failing tests", + "full_description": "Tests::Case within a context behaves like shared examples within a context has failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 35, + "run_time": 0.002861, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:3]", + "description": "has aggregated failing tests", + "full_description": "Tests::Case within a context behaves like shared examples within a context has aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 39, + "run_time": 0.00304, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:4]", + "description": "has skipped tests", + "full_description": "Tests::Case within a context behaves like shared examples within a context has skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 44, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:5]", + "description": "has passing pended tests", + "full_description": "Tests::Case within a context behaves like shared examples within a context has passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 49, + "run_time": 0.002672, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:49"] + } + }, + { + "id": "./spec/examples/class_spec.rb[1:8:7:7:6]", + "description": "has failing pended tests", + "full_description": "Tests::Case within a context behaves like shared examples within a context has failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 54, + "run_time": 0.002959, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/string_spec.rb[1:1]", + "description": "has top-level passing tests", + "full_description": "some string has top-level passing tests", + "status": "passed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 4, + "run_time": 0.00211, + "pending_message": null + }, + { + "id": "./spec/examples/string_spec.rb[1:2]", + "description": "has top-level failing tests", + "full_description": "some string has top-level failing tests", + "status": "failed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 8, + "run_time": 0.00224, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/string_spec.rb:9:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:3]", + "description": "has top-level aggregated failing tests", + "full_description": "some string has top-level aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 12, + "run_time": 0.002886, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:13:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:14:in `block (2 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:4]", + "description": "has top-level skipped tests", + "full_description": "some string has top-level skipped tests", + "status": "pending", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 17, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/string_spec.rb[1:5]", + "description": "has top-level passing pended tests", + "full_description": "some string has top-level passing pended tests", + "status": "failed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 22, + "run_time": 0.002922, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/string_spec.rb:22"] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:6]", + "description": "has top-level failing pended tests", + "full_description": "some string has top-level failing pended tests", + "status": "pending", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 27, + "run_time": 0.002704, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/string_spec.rb[1:7:1]", + "description": "has top-level passing tests", + "full_description": "some string behaves like shared examples has top-level passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 2, + "run_time": 0.0027, + "pending_message": null + }, + { + "id": "./spec/examples/string_spec.rb[1:7:2]", + "description": "has top-level failing tests", + "full_description": "some string behaves like shared examples has top-level failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 6, + "run_time": 0.002541, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:3]", + "description": "has top-level aggregated failing tests", + "full_description": "some string behaves like shared examples has top-level aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 10, + "run_time": 0.002879, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:4]", + "description": "has top-level skipped tests", + "full_description": "some string behaves like shared examples has top-level skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 15, + "run_time": 3.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/string_spec.rb[1:7:5]", + "description": "has top-level passing pended tests", + "full_description": "some string behaves like shared examples has top-level passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 20, + "run_time": 0.002721, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:20"] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:6]", + "description": "has top-level failing pended tests", + "full_description": "some string behaves like shared examples has top-level failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 25, + "run_time": 0.003249, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:1]", + "description": "has passing tests", + "full_description": "some string behaves like shared examples within a context has passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 31, + "run_time": 0.002793, + "pending_message": null + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:2]", + "description": "has failing tests", + "full_description": "some string behaves like shared examples within a context has failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 35, + "run_time": 0.002262, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:3]", + "description": "has aggregated failing tests", + "full_description": "some string behaves like shared examples within a context has aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 39, + "run_time": 0.002288, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:4]", + "description": "has skipped tests", + "full_description": "some string behaves like shared examples within a context has skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 44, + "run_time": 3.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:5]", + "description": "has passing pended tests", + "full_description": "some string behaves like shared examples within a context has passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 49, + "run_time": 0.002546, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:49"] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:7:7:6]", + "description": "has failing pended tests", + "full_description": "some string behaves like shared examples within a context has failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 54, + "run_time": 0.002156, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/string_spec.rb[1:8:1]", + "description": "has passing tests", + "full_description": "some string within a context has passing tests", + "status": "passed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 35, + "run_time": 0.002272, + "pending_message": null + }, + { + "id": "./spec/examples/string_spec.rb[1:8:2]", + "description": "has failing tests", + "full_description": "some string within a context has failing tests", + "status": "failed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 39, + "run_time": 0.002598, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/string_spec.rb:40:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:3]", + "description": "has aggregated failing tests", + "full_description": "some string within a context has aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 43, + "run_time": 0.002331, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:44:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/string_spec.rb:45:in `block (3 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:4]", + "description": "has skipped tests", + "full_description": "some string within a context has skipped tests", + "status": "pending", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 48, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/string_spec.rb[1:8:5]", + "description": "has passing pended tests", + "full_description": "some string within a context has passing pended tests", + "status": "failed", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 53, + "run_time": 0.002118, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/string_spec.rb:53"] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:6]", + "description": "has failing pended tests", + "full_description": "some string within a context has failing pended tests", + "status": "pending", + "file_path": "./spec/examples/string_spec.rb", + "line_number": 58, + "run_time": 0.002429, + "pending_message": "for a reason" + }, + { + "description": "has top-level passing tests", + "full_description": "some string within a context behaves like shared examples has top-level passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 2, + "run_time": 0.00196, + "pending_message": null + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:2]", + "description": "has top-level failing tests", + "full_description": "some string within a context behaves like shared examples has top-level failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 6, + "run_time": 0.001989, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:7:in `block (2 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:3]", + "description": "has top-level aggregated failing tests", + "full_description": "some string within a context behaves like shared examples has top-level aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 10, + "run_time": 0.002512, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:11:in `block (2 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:12:in `block (2 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:4]", + "description": "has top-level skipped tests", + "full_description": "some string within a context behaves like shared examples has top-level skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 15, + "run_time": 4.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:5]", + "description": "has top-level passing pended tests", + "full_description": "some string within a context behaves like shared examples has top-level passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 20, + "run_time": 0.002402, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:20"] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:6]", + "description": "has top-level failing pended tests", + "full_description": "some string within a context behaves like shared examples has top-level failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 25, + "run_time": 0.002197, + "pending_message": "for a reason" + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:1]", + "description": "has passing tests", + "full_description": "some string within a context behaves like shared examples within a context has passing tests", + "status": "passed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 31, + "run_time": 0.00217, + "pending_message": null + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:2]", + "description": "has failing tests", + "full_description": "some string within a context behaves like shared examples within a context has failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 35, + "run_time": 0.002212, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::ExpectationNotMetError", + "message": "\nexpected: 2\n got: 1\n\n(compared using ==)\n", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/fail_with.rb:35:in `fail_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:38:in `handle_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:56:in `block in handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:27:in `with_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/handler.rb:48:in `handle_matcher'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:65:in `to'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/expectation_target.rb:101:in `to'", + "/Users/kylekthompson/src/rwx-research/captain/spec/examples/shared_examples.rb:36:in `block (3 levels) in \u003cmain\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:263:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/rspec-rails-6.0.0.rc1/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in \u003cmodule:MinitestLifecycleAdapter\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:3]", + "description": "has aggregated failing tests", + "full_description": "some string within a context behaves like shared examples within a context has aggregated failing tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 39, + "run_time": 0.003165, + "pending_message": null, + "exception": { + "class": "RSpec::Expectations::MultipleExpectationsNotMetError", + "message": "Got 2 failures from failure aggregation block:\n\n 1) expected: 2\n got: 1\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:40:in `block (3 levels) in \u003cmain\u003e'\n\n 2) expected: 3\n got: 2\n\n (compared using ==)\n\n ./spec/examples/shared_examples.rb:41:in `block (3 levels) in \u003cmain\u003e'", + "backtrace": [ + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:102:in `block in \u003cmodule:Support\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-support-b8e2d10b0f9a/lib/rspec/support.rb:111:in `notify_failure'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:85:in `notify_aggregated_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/expectations/failure_aggregator.rb:31:in `aggregate'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-expectations-d508102d72cb/lib/rspec/matchers.rb:306:in `aggregate_failures'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2251:in `block in define_built_in_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:457:in `instance_exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:390:in `execute_with'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:352:in `call'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/hooks.rb:486:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:468:in `with_around_example_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example.rb:259:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:701:in `block in run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:697:in `run_examples'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:611:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `block in run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/example_group.rb:612:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (3 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `map'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:175:in `block (2 levels) in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/configuration.rb:2068:in `with_suite_hooks'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:170:in `block in run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/reporter.rb:74:in `report'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:169:in `run_specs'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:107:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:71:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/lib/rspec/core/runner.rb:45:in `invoke'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rspec-core-2f753b522884/exe/rspec:4:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/bin/rspec:25:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:58:in `kernel_load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli/exec.rb:23:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:484:in `exec'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:31:in `dispatch'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/cli.rb:25:in `start'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:48:in `block in \u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/3.1.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.7/libexec/bundle:36:in `\u003ctop (required)\u003e'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `load'", + "/Users/kylekthompson/.asdf/installs/ruby/3.1.2/bin/bundle:25:in `\u003cmain\u003e'" + ] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:4]", + "description": "has skipped tests", + "full_description": "some string within a context behaves like shared examples within a context has skipped tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 44, + "run_time": 3.0e-6, + "pending_message": "Temporarily skipped with xit" + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:5]", + "description": "has passing pended tests", + "full_description": "some string within a context behaves like shared examples within a context has passing pended tests", + "status": "failed", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 49, + "run_time": 0.002212, + "pending_message": "for a reason", + "exception": { + "class": "RSpec::Core::Pending::PendingExampleFixedError", + "message": "Expected example to fail since it is pending, but it passed.", + "backtrace": ["./spec/examples/shared_examples.rb:49"] + } + }, + { + "id": "./spec/examples/string_spec.rb[1:8:7:7:6]", + "description": "has failing pended tests", + "full_description": "some string within a context behaves like shared examples within a context has failing pended tests", + "status": "pending", + "file_path": "./spec/examples/shared_examples.rb", + "line_number": 54, + "run_time": 0.002257, + "pending_message": "for a reason" + } + ], + "summary": { + "duration": 0.1963, + "example_count": 72, + "failure_count": 36, + "pending_count": 24, + "errors_outside_of_examples_count": 0 + }, + "summary_line": "72 examples, 36 failures, 24 pending" +} diff --git a/internal/test/fixtures/rwx/v1.json b/internal/test/fixtures/rwx/v1.json new file mode 100644 index 0000000..d95030b --- /dev/null +++ b/internal/test/fixtures/rwx/v1.json @@ -0,0 +1,94 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "RSpec" + }, + "summary": { + "status": { "kind": "failed" }, + "tests": 3, + "otherErrors": 3, + "retries": 1, + "canceled": 0, + "failed": 1, + "pended": 0, + "quarantined": 1, + "skipped": 0, + "successful": 1, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "id": "./spec/some/path/foo_spec.rb:12", + "name": "Sky::Moon when it's dark out is bright", + "lineage": ["Sky::Moon", "when it's dark out", "is bright"], + "location": { + "file": "./spec/some/path/foo_spec.rb", + "line": 12 + }, + "attempt": { + "durationInNanoseconds": 1100300300, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/some/path/foo_spec.rb:20", + "name": "Sky::Moon when it's dark out is not bright", + "lineage": ["Sky::Moon", "when it's dark out", "is not bright"], + "location": { + "file": "./spec/some/path/foo_spec.rb", + "line": 20 + }, + "attempt": { + "durationInNanoseconds": 1200000000, + "status": { + "kind": "quarantined", + "originalStatus": { + "kind": "failed", + "message": "The moon is bright" + } + } + } + }, + { + "name": "Sky::Moon does not exist", + "attempt": { + "durationInNanoseconds": 1200000000, + "finishedAt": "2022-11-15T15:01:49Z", + "status": { "kind": "successful" }, + "meta": { "env": "foo" } + }, + "pastAttempts": [ + { + "durationInNanoseconds": 1500000000, + "finishedAt": "2022-11-15T07:01:34Z", + "status": { + "kind": "failed", + "message": "The moon is in the sky" + } + } + ] + } + ], + "otherErrors": [ + { "message": "something broke" }, + { + "message": "An error occurred", + "exception": "FooError", + "location": { + "file": "./some/path/to/file.rb", + "line": 10 + } + } + ], + "derivedFrom": [ + { + "originalFilePath": "./some/path/to/file.json", + "contents": "base64encodedoriginalfile", + "groupNumber": 1 + } + ] +} diff --git a/internal/test/fixtures/rwx/v1_not_derived.json b/internal/test/fixtures/rwx/v1_not_derived.json new file mode 100644 index 0000000..3f052f2 --- /dev/null +++ b/internal/test/fixtures/rwx/v1_not_derived.json @@ -0,0 +1,87 @@ +{ + "$schema": "https://raw.githubusercontent.com/rwx-research/test-results-schema/main/v1.json", + "framework": { + "language": "Ruby", + "kind": "RSpec" + }, + "summary": { + "status": { "kind": "failed" }, + "tests": 3, + "otherErrors": 3, + "retries": 1, + "canceled": 0, + "failed": 1, + "pended": 0, + "quarantined": 1, + "skipped": 0, + "successful": 1, + "timedOut": 0, + "todo": 0 + }, + "tests": [ + { + "id": "./spec/some/path/foo_spec.rb:12", + "name": "Sky::Moon when it's dark out is bright", + "lineage": ["Sky::Moon", "when it's dark out", "is bright"], + "location": { + "file": "./spec/some/path/foo_spec.rb", + "line": 12 + }, + "attempt": { + "durationInNanoseconds": 1100300300, + "status": { + "kind": "successful" + } + } + }, + { + "id": "./spec/some/path/foo_spec.rb:20", + "name": "Sky::Moon when it's dark out is not bright", + "lineage": ["Sky::Moon", "when it's dark out", "is not bright"], + "location": { + "file": "./spec/some/path/foo_spec.rb", + "line": 20 + }, + "attempt": { + "durationInNanoseconds": 1200000000, + "status": { + "kind": "quarantined", + "originalStatus": { + "kind": "failed", + "message": "The moon is bright" + } + } + } + }, + { + "name": "Sky::Moon does not exist", + "attempt": { + "durationInNanoseconds": 1200000000, + "finishedAt": "2022-11-15T15:01:49Z", + "status": { "kind": "successful" }, + "meta": { "env": "foo" } + }, + "pastAttempts": [ + { + "durationInNanoseconds": 1500000000, + "finishedAt": "2022-11-15T07:01:34Z", + "status": { + "kind": "failed", + "message": "The moon is in the sky" + } + } + ] + } + ], + "otherErrors": [ + { "message": "something broke" }, + { + "message": "An error occurred", + "exception": "FooError", + "location": { + "file": "./some/path/to/file.rb", + "line": 10 + } + } + ] +} diff --git a/internal/test/fixtures/unittest.xml b/internal/test/fixtures/unittest.xml new file mode 100644 index 0000000..e250626 --- /dev/null +++ b/internal/test/fixtures/unittest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/internal/test/fixtures/vitest.json b/internal/test/fixtures/vitest.json new file mode 100644 index 0000000..2139163 --- /dev/null +++ b/internal/test/fixtures/vitest.json @@ -0,0 +1,171 @@ +{ + "numTotalTestSuites": 4, + "numPassedTestSuites": 4, + "numFailedTestSuites": 0, + "numPendingTestSuites": 0, + "numTotalTests": 10, + "numPassedTests": 8, + "numFailedTests": 2, + "numPendingTests": 0, + "numTodoTests": 1, + "snapshot": { + "added": 0, + "failure": false, + "filesAdded": 0, + "filesRemoved": 0, + "filesRemovedList": [], + "filesUnmatched": 0, + "filesUpdated": 0, + "matched": 0, + "total": 0, + "unchecked": 0, + "uncheckedKeysByFile": [], + "unmatched": 0, + "updated": 0, + "didUpdate": false + }, + "startTime": 1724351128553, + "success": false, + "testResults": [ + { + "assertionResults": [ + { + "ancestorTitles": [], + "fullName": "adds 1 + 2 to equal 3", + "status": "passed", + "title": "adds 1 + 2 to equal 3", + "duration": 1, + "failureMessages": [], + "location": { + "line": 4, + "column": 1 + }, + "meta": {} + }, + { + "ancestorTitles": [], + "fullName": "it is pended", + "status": "skipped", + "title": "it is pended", + "failureMessages": [], + "location": { + "line": 8, + "column": 6 + }, + "meta": {} + }, + { + "ancestorTitles": [], + "fullName": "it is not written yet", + "status": "todo", + "title": "it is not written yet", + "failureMessages": [], + "location": { + "line": 12, + "column": 6 + }, + "meta": {} + }, + { + "ancestorTitles": ["first level of nesting"], + "fullName": "first level of nesting it passes", + "status": "passed", + "title": "it passes", + "duration": 0, + "failureMessages": [], + "location": { + "line": 15, + "column": 3 + }, + "meta": {} + }, + { + "ancestorTitles": ["first level of nesting"], + "fullName": "first level of nesting it fails", + "status": "failed", + "title": "it fails", + "duration": 2, + "failureMessages": [ + "AssertionError: expected 1 to be 2 // Object.is equality\n at /Users/kylekthompson/src/captain-examples/vitest/sum.test.js:20:15\n at file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:146:14\n at file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:61:7\n at runTest (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:960:17)\n at processTicksAndRejections (node:internal/process/task_queues:95:5)\n at runSuite (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1116:15)\n at runSuite (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1116:15)\n at runFiles (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1173:5)\n at startTests (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1182:3)\n at file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/vitest/dist/chunks/runBaseTests.CyvqmuC9.js:130:11" + ], + "location": { + "line": 19, + "column": 3 + }, + "meta": {} + }, + { + "ancestorTitles": ["first level of nesting"], + "fullName": "first level of nesting it is slow", + "status": "passed", + "title": "it is slow", + "duration": 5002, + "failureMessages": [], + "location": { + "line": 23, + "column": 3 + }, + "meta": {} + }, + { + "ancestorTitles": ["first level of nesting", "second level of nesting"], + "fullName": "first level of nesting second level of nesting it passes", + "status": "passed", + "title": "it passes", + "duration": 1, + "failureMessages": [], + "location": { + "line": 29, + "column": 5 + }, + "meta": {} + }, + { + "ancestorTitles": ["first level of nesting", "second level of nesting"], + "fullName": "first level of nesting second level of nesting it fails", + "status": "failed", + "title": "it fails", + "duration": 2, + "failureMessages": [ + "AssertionError: expected 1 to be 2 // Object.is equality\n at /Users/kylekthompson/src/captain-examples/vitest/sum.test.js:34:17\n at file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:146:14\n at file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:61:7\n at runTest (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:960:17)\n at runNextTicks (node:internal/process/task_queues:60:5)\n at listOnTimeout (node:internal/timers:538:9)\n at processTimers (node:internal/timers:512:7)\n at runSuite (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1116:15)\n at runSuite (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1116:15)\n at runSuite (file:///Users/kylekthompson/src/captain-examples/vitest/node_modules/@vitest/runner/dist/index.js:1116:15)" + ], + "location": { + "line": 33, + "column": 5 + }, + "meta": {} + }, + { + "ancestorTitles": ["first level of nesting", "second level of nesting"], + "fullName": "first level of nesting second level of nesting it is slow", + "status": "passed", + "title": "it is slow", + "duration": 5001, + "failureMessages": [], + "location": { + "line": 37, + "column": 5 + }, + "meta": {} + }, + { + "ancestorTitles": ["skipped whole nested describe"], + "fullName": "skipped whole nested describe there's a test in here though", + "status": "skipped", + "title": "there's a test in here though", + "failureMessages": [], + "location": { + "line": 45, + "column": 3 + }, + "meta": {} + } + ], + "startTime": 1724351128781, + "endTime": 1724351138790, + "status": "failed", + "message": "", + "name": "/Users/kylekthompson/src/captain-examples/vitest/sum.test.js" + } + ] +} diff --git a/internal/test/fixtures/xunit_dot_net.xml b/internal/test/fixtures/xunit_dot_net.xml new file mode 100644 index 0000000..34832ef --- /dev/null +++ b/internal/test/fixtures/xunit_dot_net.xml @@ -0,0 +1,103 @@ + + + + + + + some/path/to/NullAssertsTests.cs + 15 + + + + + + + + + + + + + + + + + + + + + + + + + + + + some/path/to/RangeAssertsTests.cs + 42 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + some/path/to/FileName.cs + 30 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +