diff --git a/tools/sdk-sync/fake-cli/git-ff-merge b/tools/sdk-sync/fake-cli/git-delete-branch similarity index 68% rename from tools/sdk-sync/fake-cli/git-ff-merge rename to tools/sdk-sync/fake-cli/git-delete-branch index d0ee7b2df3b02ce0ddfb0e4b4db396ac3cad9da9..6adc100360640fcaf9fcc575035d47c6321668ea 100755 --- a/tools/sdk-sync/fake-cli/git-ff-merge +++ b/tools/sdk-sync/fake-cli/git-delete-branch @@ -2,7 +2,7 @@ import os import sys -expected = [os.path.realpath("/tmp"), ["merge", "--ff-only", "test-branch-name"]] +expected = [os.path.realpath("/tmp"), ["branch", "-D", "test-branch-name"]] actual = [os.getcwd(), sys.argv[1:]] if expected != actual: print(f"ERROR\nExpect: {expected}\nActual: {actual}") diff --git a/tools/sdk-sync/fake-cli/git-squash-merge b/tools/sdk-sync/fake-cli/git-squash-merge new file mode 100755 index 0000000000000000000000000000000000000000..7be2a54b6b6762dbc713b78e59bb9ed2b93935e5 --- /dev/null +++ b/tools/sdk-sync/fake-cli/git-squash-merge @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +import os +import sys + +if sys.argv[1] == "merge": + expected = [os.path.realpath("/tmp"), ["merge", "--squash", "test-branch-name"]] + actual = [os.getcwd(), sys.argv[1:]] + if expected != actual: + print(f"ERROR\nExpect: {expected}\nActual: {actual}") + sys.exit(1) +else: + expected = [ + os.path.realpath("/tmp"), + ["-c", "user.name=test-author", "-c", "user.email=test-author-email", "commit", "-m", "test message"] + ] + actual = [os.getcwd(), sys.argv[1:]] + if expected != actual: + print(f"ERROR\nExpect: {expected}\nActual: {actual}") + sys.exit(1) diff --git a/tools/sdk-sync/src/git.rs b/tools/sdk-sync/src/git.rs index f9e23488eb6927549448183c56449d8c729c8a30..d114ea5ca906492df2f74a509f5c68e135035dcb 100644 --- a/tools/sdk-sync/src/git.rs +++ b/tools/sdk-sync/src/git.rs @@ -101,8 +101,17 @@ pub trait Git: Send + Sync { /// Creates a branch at the given revision. fn create_branch(&self, branch_name: &str, revision: &str) -> Result<()>; - /// Fast-forward merges a branch. - fn fast_forward_merge(&self, branch_name: &str) -> Result<()>; + /// Deletes a branch. + fn delete_branch(&self, branch_name: &str) -> Result<()>; + + /// Squash merges a branch into the current branch. + fn squash_merge( + &self, + author_name: &str, + author_email: &str, + branch_name: &str, + commit_message: &str, + ) -> Result<()>; /// Returns list of untracked files. fn untracked_files(&self) -> Result>; @@ -322,18 +331,38 @@ impl Git for GitCLI { Ok(()) } - fn fast_forward_merge(&self, branch_name: &str) -> Result<()> { + fn delete_branch(&self, branch_name: &str) -> Result<()> { let mut command = Command::new(&self.binary_name); - command.arg("merge"); - command.arg("--ff-only"); + command.arg("branch"); + command.arg("-D"); command.arg(branch_name); command.current_dir(&self.repo_path); let output = log_command(command).output()?; - handle_failure("fast_forward_merge", &output)?; + handle_failure("delete_branch", &output)?; Ok(()) } + fn squash_merge( + &self, + author_name: &str, + author_email: &str, + branch_name: &str, + commit_message: &str, + ) -> Result<()> { + let mut command = Command::new(&self.binary_name); + command.arg("merge"); + command.arg("--squash"); + command.arg(branch_name); + command.current_dir(&self.repo_path); + + let output = log_command(command).output()?; + handle_failure("squash_merge", &output)?; + + // `git merge --squash` only stages changes, so a commit is necessary after + self.commit(author_name, author_email, commit_message) + } + fn untracked_files(&self) -> Result> { let mut command = Command::new(&self.binary_name); command.arg("ls-files"); @@ -564,9 +593,21 @@ mod tests { } #[test] - fn fast_forward_merge() { - cli("git-ff-merge") - .fast_forward_merge("test-branch-name") + fn delete_branch() { + cli("git-delete-branch") + .delete_branch("test-branch-name") + .expect("successful invocation"); + } + + #[test] + fn squash_merge() { + cli("git-squash-merge") + .squash_merge( + "test-author", + "test-author-email", + "test-branch-name", + "test message", + ) .expect("successful invocation"); } } diff --git a/tools/sdk-sync/src/sync.rs b/tools/sdk-sync/src/sync.rs index 22d4d92d882239c205336655f9c4e789c214f2c9..7e9c819acfe5c51d0d55e8debc8ece1b435e3cc5 100644 --- a/tools/sdk-sync/src/sync.rs +++ b/tools/sdk-sync/src/sync.rs @@ -5,7 +5,7 @@ use self::gen::{DefaultSdkGenerator, SdkGenerator}; use crate::fs::{DefaultFs, Fs}; -use crate::git::{Commit, CommitHash, Git, GitCLI}; +use crate::git::{Commit, Git, GitCLI}; use crate::versions::{DefaultVersions, Versions, VersionsManifest}; use anyhow::{bail, Context, Result}; use smithy_rs_tool_common::macros::here; @@ -21,49 +21,12 @@ pub const BOT_NAME: &str = "AWS SDK Rust Bot"; pub const BOT_EMAIL: &str = "aws-sdk-rust-primary@amazon.com"; pub const MODEL_STASH_BRANCH_NAME: &str = "__sdk_sync__models_"; -#[cfg_attr(test, mockall::automock)] -pub trait CreateSdkGenerator: Send + std::marker::Sync { - fn create_sdk_generator( - &self, - aws_doc_sdk_examples_revision: &CommitHash, - examples_path: &Path, - fs: Arc, - reset_to_commit: Option, - original_smithy_rs_path: &Path, - ) -> Result>; -} - -pub struct DefaultCreateSdkGenerator; - -impl CreateSdkGenerator for DefaultCreateSdkGenerator { - fn create_sdk_generator( - &self, - aws_doc_sdk_examples_revision: &CommitHash, - examples_path: &Path, - fs: Arc, - reset_to_commit: Option, - original_smithy_rs_path: &Path, - ) -> Result> { - Ok(Box::new( - DefaultSdkGenerator::new( - aws_doc_sdk_examples_revision, - examples_path, - fs, - reset_to_commit, - original_smithy_rs_path, - ) - .context(here!())?, - )) - } -} - pub struct Sync { aws_doc_sdk_examples: Arc, aws_sdk_rust: Arc, smithy_rs: Arc, fs: Arc, versions: Arc, - create_sdk_generator: Arc, } impl Sync { @@ -78,7 +41,6 @@ impl Sync { smithy_rs: Arc::new(GitCLI::new(smithy_rs_path)?), fs: Arc::new(DefaultFs::new()) as Arc, versions: Arc::new(DefaultVersions::new()), - create_sdk_generator: Arc::new(DefaultCreateSdkGenerator), }) } @@ -88,7 +50,6 @@ impl Sync { smithy_rs: impl Git + 'static, fs: impl Fs + 'static, versions: impl Versions + 'static, - create_sdk_generator: impl CreateSdkGenerator + 'static, ) -> Self { Self { aws_doc_sdk_examples: Arc::new(aws_doc_sdk_examples), @@ -96,7 +57,6 @@ impl Sync { smithy_rs: Arc::new(smithy_rs), fs: Arc::new(fs), versions: Arc::new(versions), - create_sdk_generator: Arc::new(create_sdk_generator), } } @@ -155,23 +115,31 @@ impl Sync { fn sync_model_changes(&self, versions: &VersionsManifest) -> Result<()> { info!("Syncing model changes..."); - // Restore the model changes + // Restore the model changes. Note: endpoints.json/default config/model changes + // may each be in their own commits coming into this, but we want them squashed into + // one commit for smithy-rs. + self.smithy_rs + .squash_merge( + BOT_NAME, + BOT_EMAIL, + MODEL_STASH_BRANCH_NAME, + "Update SDK models", + ) + .context(here!())?; self.smithy_rs - .fast_forward_merge(MODEL_STASH_BRANCH_NAME) + .delete_branch(MODEL_STASH_BRANCH_NAME) .context(here!())?; let model_change_commit = self.smithy_rs.show("HEAD").context(here!())?; // Generate with the original examples - let sdk_gen = self - .create_sdk_generator - .create_sdk_generator( - &versions.aws_doc_sdk_examples_revision, - &self.aws_sdk_rust.path().join("examples"), - self.fs.clone(), - None, - self.smithy_rs.path(), - ) - .context(here!("failed to generate the SDK"))?; + let sdk_gen = DefaultSdkGenerator::new( + &versions.aws_doc_sdk_examples_revision, + &self.aws_sdk_rust.path().join("examples"), + self.fs.clone(), + None, + self.smithy_rs.path(), + ) + .context(here!())?; let generated_sdk = sdk_gen.generate_sdk().context(here!())?; self.copy_sdk(generated_sdk.path()) .context(here!("failed to copy the SDK"))?; @@ -214,7 +182,6 @@ impl Sync { // Generate code in parallel for each individual commit let code_gen_paths = { let smithy_rs = self.smithy_rs.clone(); - let create_sdk_generator = self.create_sdk_generator.clone(); let examples_revision = versions.aws_doc_sdk_examples_revision.clone(); let examples_path = self.aws_sdk_rust.path().join("examples"); let fs = self.fs.clone(); @@ -234,15 +201,14 @@ impl Sync { format!("couldn't find commit {} in smithy-rs", commit_hash) })?; - let sdk_gen = create_sdk_generator - .create_sdk_generator( - &examples_revision, - &examples_path, - fs.clone(), - Some(commit.hash.clone()), - smithy_rs.path(), - ) - .context(here!())?; + let sdk_gen = DefaultSdkGenerator::new( + &examples_revision, + &examples_path, + fs.clone(), + Some(commit.hash.clone()), + smithy_rs.path(), + ) + .context(here!())?; let sdk_path = sdk_gen.generate_sdk().context(here!())?; Ok((commit, sdk_path)) }) @@ -285,16 +251,14 @@ impl Sync { } let examples_head = example_revisions.iter().cloned().next().unwrap(); - let sdk_gen = self - .create_sdk_generator - .create_sdk_generator( - &examples_head, - &self.aws_doc_sdk_examples.path().join("rust_dev_preview"), - self.fs.clone(), - None, - self.smithy_rs.path(), - ) - .context(here!())?; + let sdk_gen = DefaultSdkGenerator::new( + &examples_head, + &self.aws_doc_sdk_examples.path().join("rust_dev_preview"), + self.fs.clone(), + None, + self.smithy_rs.path(), + ) + .context(here!())?; let generated_sdk = sdk_gen.generate_sdk().context(here!())?; self.copy_sdk(generated_sdk.path()) .context("failed to copy the SDK")?; @@ -445,7 +409,7 @@ impl Sync { mod tests { use super::*; use crate::fs::MockFs; - use crate::git::MockGit; + use crate::git::{CommitHash, MockGit}; use crate::versions::MockVersions; // Wish this was in std... @@ -544,7 +508,6 @@ mod tests { MockGit::new(), MockFs::new(), MockVersions::new(), - MockCreateSdkGenerator::new(), ); assert!(sync .commit_sdk_changes( @@ -596,7 +559,6 @@ mod tests { MockGit::new(), MockFs::new(), MockVersions::new(), - MockCreateSdkGenerator::new(), ); assert!(sync .commit_sdk_changes( @@ -630,7 +592,6 @@ mod tests { MockGit::new(), MockFs::new(), MockVersions::new(), - MockCreateSdkGenerator::new(), ); assert!( @@ -657,7 +618,6 @@ mod tests { MockGit::new(), MockFs::new(), MockVersions::new(), - MockCreateSdkGenerator::new(), ); assert!( @@ -684,7 +644,6 @@ mod tests { MockGit::new(), MockFs::new(), MockVersions::new(), - MockCreateSdkGenerator::new(), ); assert!(sync.sdk_has_changes().unwrap(), "it should have changes"); @@ -713,7 +672,6 @@ mod tests { MockGit::new(), MockFs::new(), MockVersions::new(), - MockCreateSdkGenerator::new(), ); assert!(sync.sdk_has_changes().unwrap(), "it should have changes"); diff --git a/tools/sdk-sync/tests/create-test-workspace b/tools/sdk-sync/tests/create-test-workspace index 5fe8ab1ee2c74908c4c0921afd7e065065bfd294..1e5c54aec4c13fa83bc58eaf0b548e1950a99bb2 100755 --- a/tools/sdk-sync/tests/create-test-workspace +++ b/tools/sdk-sync/tests/create-test-workspace @@ -18,6 +18,8 @@ if [[ $# -eq 1 && "$1" == "--with-model-changes" ]]; then INCLUDE_MODEL_CHANGES=1 fi +ENDPOINTS_JSON_PATH="aws/sdk-codegen/src/main/resources/software/amazon/smithy/rustsdk/endpoints.json" + mkdir aws-doc-sdk-examples mkdir aws-sdk-rust mkdir smithy-rs @@ -42,7 +44,9 @@ mkdir -p aws/sdk/aws-models mkdir -p aws/sdk/examples mkdir -p aws/sdk/build/aws-sdk/examples/s3 mkdir -p aws/sdk/build/aws-sdk/sdk/s3 +mkdir -p $(dirname "${ENDPOINTS_JSON_PATH}") echo "Ancient S3 model" > aws/sdk/aws-models/s3.json +echo "Old endpoints.json" > "${ENDPOINTS_JSON_PATH}" echo "Some S3 client code" > aws/sdk/build/aws-sdk/sdk/s3/fake_content cat "${SCRIPT_PATH}/fake-sdk-assemble" > gradlew chmod +x gradlew @@ -65,6 +69,8 @@ if [[ "${INCLUDE_MODEL_CHANGES}" == "1" ]]; then pushd smithy-rs echo "Updated S3 model" > aws/sdk/aws-models/s3.json git -c user.name="Automated Process" -c user.email="bot@example.com" commit -am "Update the S3 model" + echo "Updated endpoints.json" > "${ENDPOINTS_JSON_PATH}" + git -c user.name="Automated Process" -c user.email="bot@example.com" commit -am "Update endpoints.json" popd fi diff --git a/tools/sdk-sync/tests/e2e_test.rs b/tools/sdk-sync/tests/e2e_test.rs index c4fb3bd13d4ec438ebb84c8cb0d3e8f8995e69c3..981abf8643ecb782255dadb55574ea0efe74441a 100644 --- a/tools/sdk-sync/tests/e2e_test.rs +++ b/tools/sdk-sync/tests/e2e_test.rs @@ -14,6 +14,9 @@ use std::path::Path; use std::process::Command; use tempfile::TempDir; +const ENDPOINTS_JSON_PATH: &str = + "aws/sdk-codegen/src/main/resources/software/amazon/smithy/rustsdk/endpoints.json"; + static INIT_TRACING: Lazy = Lazy::new(|| { init_tracing(); true @@ -238,7 +241,7 @@ fn test_with_model_changes() { assert_eq!(BOT_NAME, sdk_commits[1].author_name); assert_eq!(BOT_EMAIL, sdk_commits[1].author_email); - assert_eq!("Update the S3 model", sdk_commits[1].message_subject); + assert_eq!("Update SDK models", sdk_commits[1].message_subject); assert_eq!("", sdk_commits[1].message_body); assert_eq!("Another Dev", sdk_commits[2].author_name); @@ -272,9 +275,13 @@ fn test_with_model_changes() { "Some modified S3 example\n", ); - // Verify smithy-rs had no changes since we don't have model updates - assert_eq!( + // Verify smithy-rs has the model updates + assert_ne!( smithy_rs_start_revision, smithy_rs.get_head_revision().unwrap() ); + assert_file_contents( + smithy_rs.path().join(ENDPOINTS_JSON_PATH), + "Updated endpoints.json\n", + ); } diff --git a/tools/sdk-sync/tests/mock_e2e_test.rs b/tools/sdk-sync/tests/mock_e2e_test.rs deleted file mode 100644 index 92bd99be158aacb715a2d7213dab58aa17dfef27..0000000000000000000000000000000000000000 --- a/tools/sdk-sync/tests/mock_e2e_test.rs +++ /dev/null @@ -1,605 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -use anyhow::Result; -use mockall::{predicate::*, Sequence}; -use once_cell::sync::Lazy; -use sdk_sync::fs::Fs; -use sdk_sync::git::{Commit, CommitHash}; -use sdk_sync::init_tracing; -use sdk_sync::sync::gen::{GeneratedSdk, SdkGenerator}; -use sdk_sync::sync::{Sync, BOT_EMAIL, BOT_NAME, MODEL_STASH_BRANCH_NAME}; -use sdk_sync::versions::VersionsManifest; -use std::path::{Path, PathBuf}; -use std::sync::Arc; - -static INIT_TRACING: Lazy = Lazy::new(|| { - init_tracing(); - true -}); - -mockall::mock! { - CreateSdkGenerator {} - impl sdk_sync::sync::CreateSdkGenerator for CreateSdkGenerator { - fn create_sdk_generator( - &self, - aws_doc_sdk_examples_revision: &CommitHash, - examples_path: &Path, - fs: Arc, - reset_to_commit: Option, - original_smithy_rs_path: &Path, - ) -> Result>; - } -} - -mockall::mock! { - Fs {} - impl sdk_sync::fs::Fs for Fs { - fn delete_all_generated_files_and_folders(&self, directory: &Path) -> Result<()>; - fn find_handwritten_files_and_folders( - &self, - aws_sdk_path: &Path, - build_artifacts_path: &Path, - ) -> Result>; - fn remove_dir_all_idempotent(&self, path: &Path) -> Result<()>; - fn read_to_string(&self, path: &Path) -> Result; - fn remove_file_idempotent(&self, path: &Path) -> Result<()>; - fn recursive_copy(&self, source: &Path, destination: &Path) -> Result<()>; - } -} - -mockall::mock! { - Git {} - impl sdk_sync::git::Git for Git { - fn path(&self) -> &Path; - fn clone_to(&self, path: &Path) -> Result<()>; - fn get_head_revision(&self) -> Result; - fn stage(&self, path: &Path) -> Result<()>; - fn commit_on_behalf( - &self, - bot_name: &str, - bot_email: &str, - author_name: &str, - author_email: &str, - message: &str, - ) -> Result<()>; - fn commit(&self, name: &str, email: &str, message: &str) -> Result<()>; - fn rev_list<'a>( - &self, - start_inclusive_revision: &str, - end_exclusive_revision: &str, - path: Option<&'a Path>, - ) -> Result>; - fn show(&self, revision: &str) -> Result; - fn hard_reset(&self, revision: &str) -> Result<()>; - fn current_branch_name(&self) -> Result; - fn create_branch(&self, branch_name: &str, revision: &str) -> Result<()>; - fn fast_forward_merge(&self, branch_name: &str) -> Result<()>; - fn untracked_files(&self) -> Result>; - fn changed_files(&self) -> Result>; - } -} - -mockall::mock! { - SdkGenerator {} - impl sdk_sync::sync::gen::SdkGenerator for SdkGenerator { - fn generate_sdk(&self) -> Result; - } -} - -mockall::mock! { - Versions {} - impl sdk_sync::versions::Versions for Versions { - fn load(&self, aws_sdk_rust_path: &Path) -> Result; - } -} - -fn set_path(mock_git: &mut MockGit, path: &str) { - mock_git.expect_path().return_const(PathBuf::from(path)); -} - -fn expect_get_head_revision(repo: &mut MockGit, seq: &mut Sequence, head: &'static str) { - repo.expect_get_head_revision() - .once() - .in_sequence(seq) - .returning(move || Ok(CommitHash::from(head))); -} - -fn expect_show_commit(repo: &mut MockGit, commit: Commit) { - let hash = commit.hash.as_ref().to_string(); - repo.expect_show() - .withf(move |h| hash == h) - .once() - .returning(move |_| Ok(commit.clone())); -} - -fn expect_hard_reset(repo: &mut MockGit, seq: &mut Sequence, hash: &str) { - let hash = hash.to_string(); - repo.expect_hard_reset() - .withf(move |h| h == hash) - .once() - .in_sequence(seq) - .returning(|_| Ok(())); -} - -fn expect_stage(repo: &mut MockGit, seq: &mut Sequence, path: &'static str) { - repo.expect_stage() - .withf(move |p| p.to_string_lossy() == path) - .once() - .in_sequence(seq) - .returning(|_| Ok(())); -} - -fn expect_has_changes(repo: &mut MockGit, seq: &mut Sequence, changes: bool) { - repo.expect_untracked_files() - .once() - .in_sequence(seq) - .returning(move || Ok(Vec::new())); - repo.expect_changed_files() - .once() - .in_sequence(seq) - .returning(move || { - Ok(if changes { - vec![PathBuf::from("some-file")] - } else { - Vec::new() - }) - }); -} - -#[derive(Default)] -struct Mocks { - aws_doc_sdk_examples: MockGit, - aws_sdk_rust: MockGit, - smithy_rs: MockGit, - fs: MockFs, - versions: MockVersions, - create_sdk_generator: MockCreateSdkGenerator, -} - -impl Mocks { - fn into_sync(self) -> Sync { - Sync::new_with( - self.aws_doc_sdk_examples, - self.aws_sdk_rust, - self.smithy_rs, - self.fs, - self.versions, - self.create_sdk_generator, - ) - } - - fn set_smithyrs_commits_to_sync( - &mut self, - previous_synced_commit: &'static str, - hashes: &'static [&'static str], - ) { - self.smithy_rs - .expect_rev_list() - .withf(move |begin, end, path| { - begin == "HEAD" && end == previous_synced_commit && path.is_none() - }) - .once() - .returning(|_, _, _| Ok(hashes.iter().map(|&hash| CommitHash::from(hash)).collect())); - } - - fn expect_recursive_copy( - &mut self, - seq: &mut Sequence, - source: &'static str, - dest: &'static str, - ) { - self.fs - .expect_recursive_copy() - .withf(move |src, dst| src.to_string_lossy() == source && dst.to_string_lossy() == dest) - .once() - .in_sequence(seq) - .returning(|_, _| Ok(())); - } - - fn expect_delete_all_generated_files_and_folders( - &mut self, - seq: &mut Sequence, - sdk_path: &'static str, - ) { - self.fs - .expect_delete_all_generated_files_and_folders() - .withf(move |p| p.to_string_lossy() == sdk_path) - .once() - .in_sequence(seq) - .returning(|_| Ok(())); - } - - fn expect_find_handwritten_files_and_folders( - &mut self, - seq: &mut Sequence, - sdk_path: &'static str, - artifacts_path: &'static str, - files: &'static [&'static str], - ) { - self.fs - .expect_find_handwritten_files_and_folders() - .withf(move |aws_sdk_p, artifacts_p| { - aws_sdk_p.to_string_lossy() == sdk_path - && artifacts_p.to_string_lossy() == artifacts_path - }) - .once() - .in_sequence(seq) - .returning(move |_, _| Ok(files.iter().map(PathBuf::from).collect())); - } -} - -fn expect_copy_sdk(mocks: &mut Mocks) { - let mut seq = Sequence::new(); - mocks.expect_delete_all_generated_files_and_folders(&mut seq, "/p2/aws-sdk-rust"); - mocks.expect_find_handwritten_files_and_folders( - &mut seq, - "/p2/aws-sdk-rust", - "/p2/some-temp-cloned-smithy-rs/aws/sdk/build/aws-sdk", - &[], // no handwritten files found - ); - mocks.expect_recursive_copy( - &mut seq, - "/p2/some-temp-cloned-smithy-rs/aws/sdk/build/aws-sdk/.", - "/p2/aws-sdk-rust", - ); -} - -fn expect_generate_sdk( - mocks: &mut Mocks, - expected_examples_revision: &'static str, - expected_examples_path: &'static str, - expected_reset_to_commit: Option<&str>, - expected_original_smithy_rs_path: &'static str, -) { - let expected_reset_to_commit = expected_reset_to_commit.map(|c| CommitHash::from(c)); - mocks - .create_sdk_generator - .expect_create_sdk_generator() - .withf( - move |examples_revision, - examples_path, - _fs, - reset_to_commit, - original_smithy_rs_path| { - examples_revision.as_ref() == expected_examples_revision - && examples_path.to_string_lossy() == expected_examples_path - && reset_to_commit == &expected_reset_to_commit - && original_smithy_rs_path.to_string_lossy() == expected_original_smithy_rs_path - }, - ) - .once() - .returning(|_, _, _, _, _| { - let mut mock = MockSdkGenerator::new(); - mock.expect_generate_sdk().once().returning(|| { - Ok(GeneratedSdk::new( - "/p2/some-temp-cloned-smithy-rs/aws/sdk/build/aws-sdk", - )) - }); - Ok(Box::new(mock)) - }); -} - -fn expect_successful_smithyrs_sync( - mocks: &mut Mocks, - seq: &mut Sequence, - commit: Commit, - expected_commit_message: &str, -) { - expect_show_commit(&mut mocks.smithy_rs, commit.clone()); - expect_generate_sdk( - mocks, - "old-examples-hash", - "/p2/aws-sdk-rust/examples", - Some(commit.hash.as_ref()), - "/p2/smithy-rs", - ); - expect_copy_sdk(mocks); - - // Commit generated SDK - expect_has_changes(&mut mocks.aws_sdk_rust, seq, true); - expect_stage(&mut mocks.aws_sdk_rust, seq, "."); - let expected_commit_message = expected_commit_message.to_string(); - mocks - .aws_sdk_rust - .expect_commit_on_behalf() - .withf( - move |bot_name, bot_email, author_name, author_email, message| { - bot_name == BOT_NAME - && bot_email == BOT_EMAIL - && author_name == commit.author_name - && author_email == commit.author_email - && message == expected_commit_message - }, - ) - .once() - .returning(|_, _, _, _, _| Ok(())); - mocks - .aws_sdk_rust - .expect_get_head_revision() - .once() - .returning(|| Ok(CommitHash::from("newly-synced-hash"))); -} - -fn expect_successful_example_sync( - mocks: &mut Mocks, - seq: &mut Sequence, - old_examples_hash: &'static str, - example_commits: &[Commit], -) { - // Example revision discovery - { - let example_commits = example_commits.to_vec(); - mocks - .aws_doc_sdk_examples - .expect_rev_list() - .withf(move |begin, end, path| { - begin == "HEAD" - && end == old_examples_hash - && *path == Some(&PathBuf::from("rust_dev_preview")) - }) - .once() - .in_sequence(seq) - .returning(move |_, _, _| { - Ok(example_commits.iter().cloned().map(|c| c.hash).collect()) - }); - } - - // Codegen - expect_generate_sdk( - mocks, - "hash2", - "/p2/aws-doc-sdk-examples/rust_dev_preview", - None, - "/p2/smithy-rs", - ); - expect_copy_sdk(mocks); - - // Commit generated SDK - expect_stage(&mut mocks.aws_sdk_rust, seq, "."); - for commit in example_commits { - expect_show_commit(&mut mocks.aws_doc_sdk_examples, commit.clone()); - } - mocks - .aws_sdk_rust - .expect_commit() - .withf(|name, email, message| { - name == BOT_NAME - && email == BOT_EMAIL - && message.starts_with("[examples] Sync SDK examples") - }) - .once() - .in_sequence(seq) - .returning(|_, _, _| Ok(())); -} - -fn expect_model_changes(mocks: &mut Mocks, seq: &mut Sequence, models_changed: bool) { - let (old_hash, new_hash) = if models_changed { - ("with-new-models", "without-new-models") - } else { - ("no-new-models", "no-new-models") - }; - expect_get_head_revision(&mut mocks.smithy_rs, seq, old_hash); - - mocks - .smithy_rs - .expect_create_branch() - .with(eq(MODEL_STASH_BRANCH_NAME), eq("HEAD")) - .once() - .in_sequence(seq) - .returning(|_, _| Ok(())); - - mocks - .smithy_rs - .expect_current_branch_name() - .once() - .in_sequence(seq) - .returning(|| Ok("main".to_string())); - - expect_hard_reset(&mut mocks.smithy_rs, seq, "origin/main"); - expect_get_head_revision(&mut mocks.smithy_rs, seq, new_hash); -} - -fn expect_sync_model_changes(mocks: &mut Mocks, seq: &mut Sequence) { - // Expect merge of the models back into the main branch - mocks - .smithy_rs - .expect_fast_forward_merge() - .with(eq(MODEL_STASH_BRANCH_NAME)) - .once() - .in_sequence(seq) - .returning(|_| Ok(())); - - // HEAD has the model changes; set the commit info for it - expect_show_commit( - &mut mocks.smithy_rs, - Commit { - hash: "HEAD".into(), - author_name: BOT_NAME.into(), - author_email: BOT_EMAIL.into(), - message_subject: "Some model changes".into(), - message_body: "".into(), - }, - ); - - // Codegen - expect_generate_sdk( - mocks, - "old-examples-hash", - "/p2/aws-sdk-rust/examples", - None, - "/p2/smithy-rs", - ); - expect_copy_sdk(mocks); - - // Commit generated SDK - expect_has_changes(&mut mocks.aws_sdk_rust, seq, true); - expect_stage(&mut mocks.aws_sdk_rust, seq, "."); - mocks - .aws_sdk_rust - .expect_commit() - .withf(|name, email, message| { - name == BOT_NAME && email == BOT_EMAIL && message.starts_with("Some model changes") - }) - .once() - .in_sequence(seq) - .returning(|_, _, _| Ok(())); -} - -#[test] -fn mocked_e2e_without_model_changes() { - assert!(*INIT_TRACING); - let mut mocks = Mocks::default(); - let mut seq = Sequence::new(); - - set_path(&mut mocks.aws_doc_sdk_examples, "/p2/aws-doc-sdk-examples"); - set_path(&mut mocks.aws_sdk_rust, "/p2/aws-sdk-rust"); - set_path(&mut mocks.smithy_rs, "/p2/smithy-rs"); - - mocks - .versions - .expect_load() - .withf(|p| p.to_string_lossy() == "/p2/aws-sdk-rust") - .once() - .returning(|_| { - Ok(VersionsManifest { - smithy_rs_revision: "some-previous-commit-hash".into(), - aws_doc_sdk_examples_revision: "old-examples-hash".into(), - }) - }); - mocks - .set_smithyrs_commits_to_sync("some-previous-commit-hash", &["hash-newest", "hash-oldest"]); - - expect_model_changes(&mut mocks, &mut seq, false); - - expect_successful_smithyrs_sync( - &mut mocks, - &mut seq, - Commit { - hash: "hash-oldest".into(), - author_name: "Some Dev".into(), - author_email: "somedev@example.com".into(), - message_subject: "Some commit subject".into(), - message_body: "".into(), - }, - "[smithy-rs] Some commit subject", - ); - expect_successful_smithyrs_sync( - &mut mocks, - &mut seq, - Commit { - hash: "hash-newest".into(), - author_name: "Another Dev".into(), - author_email: "anotherdev@example.com".into(), - message_subject: "Another commit subject".into(), - message_body: "This one has a body\n\n- bullet\n- bullet\n\nmore".into(), - }, - "[smithy-rs] Another commit subject\n\nThis one has a body\n\n- bullet\n- bullet\n\nmore", - ); - - expect_successful_example_sync( - &mut mocks, - &mut seq, - "old-examples-hash", - &[ - Commit { - hash: "hash2".into(), - author_name: "Some Example Writer".into(), - author_email: "someexamplewriter@example.com".into(), - message_subject: "More examples".into(), - message_body: "".into(), - }, - Commit { - hash: "hash1".into(), - author_name: "Another Example Writer".into(), - author_email: "anotherexamplewriter@example.com".into(), - message_subject: "Another example".into(), - message_body: "This one has a body\n\n- bullet\n- bullet\n\nmore".into(), - }, - ], - ); - - let sync = mocks.into_sync(); - sync.sync().expect("success"); -} - -#[test] -fn mocked_e2e_with_model_changes() { - assert!(*INIT_TRACING); - let mut mocks = Mocks::default(); - let mut seq = Sequence::new(); - - set_path(&mut mocks.aws_doc_sdk_examples, "/p2/aws-doc-sdk-examples"); - set_path(&mut mocks.aws_sdk_rust, "/p2/aws-sdk-rust"); - set_path(&mut mocks.smithy_rs, "/p2/smithy-rs"); - - mocks - .versions - .expect_load() - .withf(|p| p.to_string_lossy() == "/p2/aws-sdk-rust") - .once() - .returning(|_| { - Ok(VersionsManifest { - smithy_rs_revision: "some-previous-commit-hash".into(), - aws_doc_sdk_examples_revision: "old-examples-hash".into(), - }) - }); - mocks - .set_smithyrs_commits_to_sync("some-previous-commit-hash", &["hash-newest", "hash-oldest"]); - - expect_model_changes(&mut mocks, &mut seq, true); - - expect_successful_smithyrs_sync( - &mut mocks, - &mut seq, - Commit { - hash: "hash-oldest".into(), - author_name: "Some Dev".into(), - author_email: "somedev@example.com".into(), - message_subject: "Some commit subject".into(), - message_body: "".into(), - }, - "[smithy-rs] Some commit subject", - ); - expect_successful_smithyrs_sync( - &mut mocks, - &mut seq, - Commit { - hash: "hash-newest".into(), - author_name: "Another Dev".into(), - author_email: "anotherdev@example.com".into(), - message_subject: "Another commit subject".into(), - message_body: "This one has a body\n\n- bullet\n- bullet\n\nmore".into(), - }, - "[smithy-rs] Another commit subject\n\nThis one has a body\n\n- bullet\n- bullet\n\nmore", - ); - - expect_sync_model_changes(&mut mocks, &mut seq); - - expect_successful_example_sync( - &mut mocks, - &mut seq, - "old-examples-hash", - &[ - Commit { - hash: "hash2".into(), - author_name: "Some Example Writer".into(), - author_email: "someexamplewriter@example.com".into(), - message_subject: "More examples".into(), - message_body: "".into(), - }, - Commit { - hash: "hash1".into(), - author_name: "Another Example Writer".into(), - author_email: "anotherexamplewriter@example.com".into(), - message_subject: "Another example".into(), - message_body: "This one has a body\n\n- bullet\n- bullet\n\nmore".into(), - }, - ], - ); - - let sync = mocks.into_sync(); - sync.sync().expect("success"); -}