Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,42 @@ That means Limmat won't re-run tests unless the actual repository contents
change - for example changes to the commit message won't invalidate cache
results.

For fine-grained cache control, you can use `cache = "by_commit_with_notes"`.
This creates cache keys based on both the commit hash and git notes attached
to the commit under the `refs/notes/limmat` ref. This allows you to
invalidate the cache for specific commits by adding or modifying notes:

```bash
# This will invalidate the cache for this commit
git notes --ref=limmat add -m "force rebuild" abc1234

# Different note content = different cache key
git notes --ref=limmat add -m "test with flag X" abc1234 --force
```

Your test scripts can access the exact notes content that was used for the cache key:

```bash
#!/bin/bash
# Example test script using git notes for configuration

if [[ -n "$LIMMAT_NOTES_OBJECT" ]]; then
# Get the exact notes content used for cache key generation
notes_content=$(git cat-file -p "$LIMMAT_NOTES_OBJECT")
echo "Test configuration from notes: $notes_content"

# Parse test parameters from notes
if echo "$notes_content" | grep -q "rebuild"; then
echo "Force rebuild requested"
make clean
fi
else
echo "No notes attached to this commit, using defaults"
fi

make test
```
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rework the docs.


Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to integrate it into limmat test

If the test is terminated by a signal, it isn't considered to have produced a
result: instead of "success" or "failure" it's an "error". Errors aren't cached.

Expand Down Expand Up @@ -323,6 +359,7 @@ These environment variables are passed to your job.
| `LIMMAT_ORIGIN` | Path of the main repository worktree (i.e. `--repo`). |
| `LIMMAT_COMMIT` | Hash of the commit to be tested. |
| `LIMMAT_CONFIG` | Path of the config file. |
| `LIMMAT_NOTES_OBJECT` | Git object hash of the notes content used for cache key generation (when using `cache = "by_commit_with_notes"`). Empty if no notes exist. |
| `LIMMAT_RESOURCE_<resource_name>_<n>` | Values for [resources](#resources) used by the test. |
| `LIMMAT_RESOURCE_<resource_name>` | If the test only uses one of a resource, shorthand for `LIMMAT_RESOURCE_<resource_name>_0` |
| `LIMMAT_ARTIFACTS_<job_name>` | If the test depends on `job_name`, this directory contains that job's [artifacts](#artifacts). |
Expand Down Expand Up @@ -376,6 +413,13 @@ make -j defconfig
make -j16 vmlinux O=$LIMMAT_ARTIFACTS
"""

# Run stress tests that can be configured via git notes
[[tests]]
name = "stress_test"
cache = "by_commit_with_notes"
command = "stress_test.sh"
depends_on = ["kbuild"]

# Check the locally-built kernel boots in a QEMU VM.
[[tests]]
name = "boot_qemu"
Expand Down
4 changes: 3 additions & 1 deletion limmat.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"enum": [
"no_caching",
"by_commit",
"by_tree"
"by_tree",
"by_commit_with_notes"
]
},
"Command": {
Expand Down Expand Up @@ -103,6 +104,7 @@
],
"properties": {
"cache": {
"description": "Controls when test results are cached. \"by_commit\" (default) caches by commit hash. \"by_tree\" caches by tree hash (ignores commit message changes). \"by_commit_with_notes\" caches by commit hash plus git notes under refs/notes/limmat. \"no_caching\" disables caching.",
"default": "by_commit",
"allOf": [
{
Expand Down
1 change: 1 addition & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ pub struct Test {
/// willing to wait when you terminate this program.
shutdown_grace_period_s: u64,
#[serde(default = "default_cache_policy")]
/// Controls when test results are cached. "by_commit" (default) caches by commit hash. "by_tree" caches by tree hash (ignores commit message changes). "by_commit_with_notes" caches by commit hash plus git notes under refs/notes/limmat. "no_caching" disables caching.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document on the enum

cache: CachePolicy,
#[serde(default)]
depends_on: Vec<String>,
Expand Down
68 changes: 67 additions & 1 deletion src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ impl Worktree for PersistentWorktree {
pub struct Commit {
pub hash: CommitHash,
pub tree: TreeHash,
pub limmat_notes_object: Option<Hash>,
}

impl Commit {
Expand All @@ -180,6 +181,7 @@ impl Commit {
Self {
hash: CommitHash::new("080b8ecbad3e34e55c5a035af80100f73b742a8d"),
tree: TreeHash::new("6366d790125291272542a6b40f6fd3400e080821"),
limmat_notes_object: None,
}
}
}
Expand Down Expand Up @@ -457,6 +459,65 @@ pub trait Worktree: Debug + Sync {
})
}

async fn get_notes_object_hash(
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document. Make notes ref configurable.

&self,
commit_hash: &CommitHash,
) -> anyhow::Result<Option<Hash>> {
debug!(
"Looking for git notes for commit {}",
commit_hash.as_ref() as &str
);
let output = self
.git(["notes", "--ref=limmat", "list"])
.await
.arg(commit_hash.as_ref() as &str)
.output()
.await
.context("failed to run 'git notes list'")?;

debug!("git notes list exit code: {:?}", output.status.code());
debug!(
"git notes list stdout: {:?}",
String::from_utf8_lossy(&output.stdout)
);
debug!(
"git notes list stderr: {:?}",
String::from_utf8_lossy(&output.stderr)
);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove most or all of the debug logs in here.


// Exit code 1 means no notes found, which is fine
if output.status.code() == Some(1) {
debug!("No notes found for commit {}", commit_hash.as_ref() as &str);
return Ok(None);
}

if !output.status.success() {
// Other error - treat as no notes to be safe
debug!(
"git notes list failed with code {:?}, treating as no notes",
output.status.code()
);
return Ok(None);
}

let notes_output = String::from_utf8_lossy(&output.stdout);
let notes_object_hash = notes_output.trim();
if notes_object_hash.is_empty() {
debug!(
"Empty notes output for commit {}",
commit_hash.as_ref() as &str
);
return Ok(None);
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's treat empty output as an error here too


debug!(
"Found notes object hash {} for commit {}",
notes_object_hash,
commit_hash.as_ref() as &str
);
Ok(Some(Hash::new(notes_object_hash.to_string())))
}

// None means we successfully looked it up but it didn't exist.
async fn rev_parse<S>(&self, rev_spec: S) -> anyhow::Result<Option<Commit>>
where
Expand Down Expand Up @@ -486,9 +547,14 @@ pub trait Worktree: Debug + Sync {
String::from_utf8_lossy(&output.stderr)
);
}

let commit_hash = CommitHash::new(parts[0]);
let limmat_notes_object = self.get_notes_object_hash(&commit_hash).await?;

Ok(Some(Commit {
hash: CommitHash::new(parts[0]),
hash: commit_hash,
tree: TreeHash::new(parts[1]),
limmat_notes_object,
}))
}
}
Expand Down
Loading
Loading