[#601] Added 'QueueTrait' with Drupal core queue management and assertion steps.#604
[#601] Added 'QueueTrait' with Drupal core queue management and assertion steps.#604AlexSkrypnyk merged 4 commits intomainfrom
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 1 minutes and 21 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughAdds a new Drupal Changes
Sequence DiagramsequenceDiagram
participant Behat as Behat Engine
participant FC as FeatureContext
participant QT as QueueTrait
participant DrupalQueue as Drupal Queue API
participant QueueWorker as Queue Worker
Behat->>FC: Given the "X" queue is empty
FC->>QT: queueEmpty("X")
QT->>QT: queueTrackName("X")
QT->>DrupalQueue: deleteQueue("X")
QT->>DrupalQueue: createQueue("X")
Behat->>FC: When I process 5 items from "X"
FC->>QT: queueProcessItems(5, "X")
loop up to 5 times
QT->>DrupalQueue: claimItem(leaseTime)
DrupalQueue-->>QT: item or FALSE
alt item claimed
QT->>QueueWorker: processItem(item.data)
QueueWorker-->>QT: processed
QT->>DrupalQueue: deleteItem(item)
else no item
QT-->>FC: throw RuntimeException
end
end
Behat->>FC: Then the "X" queue should have 2 items
FC->>QT: queueAssertItemCount("X", 2)
QT->>DrupalQueue: numberOfItems("X")
DrupalQueue-->>QT: count
QT-->>FC: assert / throw ExpectationException
Note over QT,DrupalQueue: AfterScenario('@queue') -> delete tracked queues and reset list
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Drupal/QueueTrait.php`:
- Around line 175-182: Rename the parameter of the protected helper method
queueTrackName from $queue_name to camelCase $queueName and update all uses
inside the method to match (e.g., the in_array check and array push against
$this->queueNames). Ensure the method signature reads queueTrackName(string
$queueName): void and that any callers (if any within the class) are updated to
pass the same variable name to avoid mismatches.
- Around line 57-72: In queueProcessItems, add defensive handling around the
plugin manager call that creates the worker (the createInstance($queue) call) so
a missing queue worker (PluginNotFoundException) is caught and replaced with a
clear, actionable error: wrap the createInstance($queue) invocation in a
try/catch for PluginNotFoundException and either throw a RuntimeException with a
friendly message including the $queue name (or log and return) so callers see
the real problem; also update the method docblock to state that a queue worker
plugin with an ID matching $queue is required.
In `@tests/behat/features/drupal_queue.feature`:
- Around line 24-28: Add a singular-friendly Behat step alias so the existing
step definition in FeatureContextTrait.php accepts both "item" and "items": add
a Given annotation that matches "I add :count item(s) to the :queue queue" (or
equivalent regex) alongside the current "I add :count items" definition, keeping
the same handler method name (the existing step handler in FeatureContextTrait)
so behavior is unchanged but the feature file can use the grammatically correct
"I add 1 item to the :queue queue".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 68ce7dbc-a32f-4c19-8bd8-c85dac028a2a
📒 Files selected for processing (6)
README.mdSTEPS.mdsrc/Drupal/QueueTrait.phptests/behat/bootstrap/FeatureContext.phptests/behat/bootstrap/FeatureContextTrait.phptests/behat/features/drupal_queue.feature
| public function queueProcessItems(int $count, string $queue): void { | ||
| $this->queueTrackName($queue); | ||
| $queue_instance = \Drupal::service('queue')->get($queue); | ||
| $worker = \Drupal::service('plugin.manager.queue_worker')->createInstance($queue); | ||
| $lease_time = $this->queueGetLeaseTime(); | ||
|
|
||
| $processed = 0; | ||
| while ($processed < $count) { | ||
| $item = $queue_instance->claimItem($lease_time); | ||
| if (!$item) { | ||
| throw new \RuntimeException(sprintf('Queue "%s" has no more items to process. Processed %d of %d requested items.', $queue, $processed, $count)); | ||
| } | ||
| $worker->processItem($item->data); | ||
| $queue_instance->deleteItem($item); | ||
| $processed++; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider handling missing queue worker gracefully.
The method assumes a queue worker plugin exists with an ID matching the queue name. If no such worker exists, createInstance($queue) throws a PluginNotFoundException with a potentially confusing message.
Consider adding explicit error handling or documenting this requirement in the docblock.
♻️ Proposed defensive handling
#[When('I process :count item(s) from the :queue queue')]
public function queueProcessItems(int $count, string $queue): void {
$this->queueTrackName($queue);
$queue_instance = \Drupal::service('queue')->get($queue);
- $worker = \Drupal::service('plugin.manager.queue_worker')->createInstance($queue);
+ $worker_manager = \Drupal::service('plugin.manager.queue_worker');
+ if (!$worker_manager->hasDefinition($queue)) {
+ throw new \RuntimeException(sprintf('No queue worker plugin found for queue "%s". Ensure a queue worker with this ID exists.', $queue));
+ }
+ $worker = $worker_manager->createInstance($queue);
$lease_time = $this->queueGetLeaseTime();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public function queueProcessItems(int $count, string $queue): void { | |
| $this->queueTrackName($queue); | |
| $queue_instance = \Drupal::service('queue')->get($queue); | |
| $worker = \Drupal::service('plugin.manager.queue_worker')->createInstance($queue); | |
| $lease_time = $this->queueGetLeaseTime(); | |
| $processed = 0; | |
| while ($processed < $count) { | |
| $item = $queue_instance->claimItem($lease_time); | |
| if (!$item) { | |
| throw new \RuntimeException(sprintf('Queue "%s" has no more items to process. Processed %d of %d requested items.', $queue, $processed, $count)); | |
| } | |
| $worker->processItem($item->data); | |
| $queue_instance->deleteItem($item); | |
| $processed++; | |
| } | |
| public function queueProcessItems(int $count, string $queue): void { | |
| $this->queueTrackName($queue); | |
| $queue_instance = \Drupal::service('queue')->get($queue); | |
| $worker_manager = \Drupal::service('plugin.manager.queue_worker'); | |
| if (!$worker_manager->hasDefinition($queue)) { | |
| throw new \RuntimeException(sprintf('No queue worker plugin found for queue "%s". Ensure a queue worker with this ID exists.', $queue)); | |
| } | |
| $worker = $worker_manager->createInstance($queue); | |
| $lease_time = $this->queueGetLeaseTime(); | |
| $processed = 0; | |
| while ($processed < $count) { | |
| $item = $queue_instance->claimItem($lease_time); | |
| if (!$item) { | |
| throw new \RuntimeException(sprintf('Queue "%s" has no more items to process. Processed %d of %d requested items.', $queue, $processed, $count)); | |
| } | |
| $worker->processItem($item->data); | |
| $queue_instance->deleteItem($item); | |
| $processed++; | |
| } |
🧰 Tools
🪛 PHPMD (2.15.0)
[error] 67-67: Missing class import via use statement (line '67', column '19'). (undefined)
(MissingImport)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Drupal/QueueTrait.php` around lines 57 - 72, In queueProcessItems, add
defensive handling around the plugin manager call that creates the worker (the
createInstance($queue) call) so a missing queue worker (PluginNotFoundException)
is caught and replaced with a clear, actionable error: wrap the
createInstance($queue) invocation in a try/catch for PluginNotFoundException and
either throw a RuntimeException with a friendly message including the $queue
name (or log and return) so callers see the real problem; also update the method
docblock to state that a queue worker plugin with an ID matching $queue is
required.
There was a problem hiding this comment.
Fixed in 1ab890d. Changed test helper annotation to tuple format 'I add :count item(s) to the :queue queue' and updated feature file to use singular form.
There was a problem hiding this comment.
@AlexSkrypnyk, it looks like the fix you described — changing the test helper annotation to tuple format 'I add :count item(s) to the :queue queue' and updating the feature file for singular form — addresses a different concern (step annotation format), rather than this specific comment.
This comment was about adding defensive handling for a missing queue worker plugin in queueProcessItems() (and queueProcessAll()). Specifically, adding a hasDefinition($queue) check before calling createInstance($queue) so that a missing worker results in a clear, actionable error instead of a raw PluginNotFoundException.
Could you confirm whether that defensive handling was also included in commit 1ab890d, or if it's still pending?
🧠 Learnings used
Learnt from: AlexSkrypnyk
Repo: drevops/behat-steps PR: 592
File: tests/behat/features/table.feature:6-7
Timestamp: 2026-04-09T12:24:19.008Z
Learning: In drevops/behat-steps Behat feature files (tests/behat/features/**/*.feature), the `trait:TraitName` tag is used ONLY for negative/failure scenarios that run behat-within-behat via BehatCliContext (i.e., scenarios that invoke `behat --no-colors` to assert expected failure output). Positive "works as expected" scenarios that run directly against the Drupal site use only the `api` tag. Do NOT suggest adding `trait:TraitName` to positive scenarios — it is intentionally absent from them. This pattern is consistent across all trait test files (e.g., element.feature, drupal_content.feature, table.feature).
Learnt from: CR
Repo: drevops/behat-steps PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T22:14:22.972Z
Learning: Consolidate related tests into existing feature files rather than creating new ones
Learnt from: AlexSkrypnyk
Repo: drevops/behat-steps PR: 592
File: tests/behat/features/table.feature:38-40
Timestamp: 2026-04-09T12:25:05.364Z
Learning: In drevops/behat-steps, within `trait:*` negative scenarios in `tests/behat/features/**/*.feature`, `page content:` (and other DrupalContext content-creation steps like `landing_page content:`) are available and work correctly when the nested steps are written under `scenario steps tagged with "api":`. This is because BehatCliContext generates a feature file tagged with `api`, which makes the full DrupalContext (including `createNodes()`) available. The guideline "avoid custom steps in negative tests" does NOT apply to content-creation steps when `scenario steps tagged with "api":` is used.
Learnt from: AlexSkrypnyk
Repo: drevops/behat-steps PR: 555
File: tests/behat/features/metatag.feature:52-60
Timestamp: 2026-04-08T07:00:13.988Z
Learning: Repository convention (tests/behat/features/**/*.feature): Positive scenarios that exercise static fixtures (no Drupal API), such as visiting tests/behat/fixtures/*.html, are intentionally left untagged. Tags like api or trait:TraitName are used for integration/negative or trait-scoped scenarios only (e.g., in element.feature and metatag.feature).
Learnt from: AlexSkrypnyk
Repo: drevops/behat-steps PR: 560
File: src/ResponsiveTrait.php:173-173
Timestamp: 2026-04-09T08:32:59.254Z
Learning: In this project (drevops/behat-steps), Behat hook methods annotated with attributes like #[BeforeStep], #[BeforeScenario] (and similar Behat hook attributes) must keep their required scope parameter in the method signature (e.g., `BeforeStepScope $scope`, `BeforeScenarioScope $scope`) as required by the Behat framework contract. Even if the parameter is not referenced inside the method body, do not rename it to an unused placeholder like `$_scope` or otherwise change its type/name—treat it as structurally required. PHPMD `UnusedFormalParameter` warnings on these specific hook-method scope parameters are expected false positives and should be ignored.
| /** | ||
| * Track a queue name for cleanup. | ||
| */ | ||
| protected function queueTrackName(string $queue_name): void { | ||
| if (!in_array($queue_name, $this->queueNames)) { | ||
| $this->queueNames[] = $queue_name; | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Rename parameter to follow camelCase convention for non-step methods.
Per coding guidelines, local variables in Behat step methods should use snake_case, but method parameters in protected helper methods should use camelCase.
✨ Proposed fix
- protected function queueTrackName(string $queue_name): void {
- if (!in_array($queue_name, $this->queueNames)) {
- $this->queueNames[] = $queue_name;
+ protected function queueTrackName(string $queueName): void {
+ if (!in_array($queueName, $this->queueNames)) {
+ $this->queueNames[] = $queueName;
}
}As per coding guidelines: "Local variables and method arguments in Behat steps should use snake_case, while method names and class properties should use camelCase."
🧰 Tools
🪛 PHPMD (2.15.0)
[error] 178-182: The parameter $queue_name is not named in camelCase. (undefined)
(CamelCaseParameterName)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Drupal/QueueTrait.php` around lines 175 - 182, Rename the parameter of
the protected helper method queueTrackName from $queue_name to camelCase
$queueName and update all uses inside the method to match (e.g., the in_array
check and array push against $this->queueNames). Ensure the method signature
reads queueTrackName(string $queueName): void and that any callers (if any
within the class) are updated to pass the same variable name to avoid
mismatches.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #604 +/- ##
==========================================
- Coverage 99.57% 98.44% -1.13%
==========================================
Files 38 40 +2
Lines 2573 2636 +63
==========================================
+ Hits 2562 2595 +33
- Misses 11 41 +30 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/Drupal/QueueTrait.php (2)
180-183:⚠️ Potential issue | 🟡 MinorResolve the helper parameter naming warning.
Line 180 uses
$queue_namein a protected helper method, leaving the CamelCaseParameterName warning unresolved. Rename to camelCase and update references.♻️ Suggested fix
- protected function queueTrackName(string $queue_name): void { - if (!in_array($queue_name, $this->queueNames)) { - $this->queueNames[] = $queue_name; + protected function queueTrackName(string $queueName): void { + if (!in_array($queueName, $this->queueNames, TRUE)) { + $this->queueNames[] = $queueName; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Drupal/QueueTrait.php` around lines 180 - 183, The helper method queueTrackName uses a snake_case parameter $queue_name which triggers a CamelCaseParameterName warning; rename the parameter to $queueName in the queueTrackName method signature and update all internal references in that method (and any callers) to use $queueName so the code compiles and the $this->queueNames usage remains unchanged.
57-61:⚠️ Potential issue | 🟠 MajorHandle missing queue-worker plugins with an actionable error.
Line 60 and Line 87 call
createInstance($queue)without guarding missing plugin definitions. A missing worker ID currently bubbles a low-level plugin exception; wrap it and throw a clear runtime error with the queue name.♻️ Suggested fix
+use Drupal\Component\Plugin\Exception\PluginNotFoundException; ... public function queueProcessItems(int $count, string $queue): void { $this->queueTrackName($queue); $queue_instance = \Drupal::service('queue')->get($queue); - $worker = \Drupal::service('plugin.manager.queue_worker')->createInstance($queue); + try { + $worker = \Drupal::service('plugin.manager.queue_worker')->createInstance($queue); + } + catch (PluginNotFoundException $e) { + throw new \RuntimeException(sprintf('No queue worker plugin found for queue "%s".', $queue), 0, $e); + } ... public function queueProcessAll(string $queue): void { $this->queueTrackName($queue); $queue_instance = \Drupal::service('queue')->get($queue); - $worker = \Drupal::service('plugin.manager.queue_worker')->createInstance($queue); + try { + $worker = \Drupal::service('plugin.manager.queue_worker')->createInstance($queue); + } + catch (PluginNotFoundException $e) { + throw new \RuntimeException(sprintf('No queue worker plugin found for queue "%s".', $queue), 0, $e); + }Also applies to: 84-88
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Drupal/QueueTrait.php` around lines 57 - 61, When calling the plugin manager's createInstance($queue) in queueProcessItems, wrap the createInstance(...) call in a try/catch and throw a clear RuntimeException that includes the queue name (e.g. "No queue worker defined for queue '{$queue}'") instead of letting the low-level plugin exception bubble; catch the specific plugin exception if available (or \Exception) and rethrow a \RuntimeException with the queue name and the caught exception as previous. Apply the same guard to the other createInstance($queue) usage in this trait so both places produce an actionable error mentioning the queue ID.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Drupal/QueueTrait.php`:
- Around line 91-105: The safety check currently throws whenever $processed >=
$limit even if the queue was fully drained; update the post-loop check so we
only throw when the limit was hit and there are still items remaining: after the
loop, if ($processed >= $limit) attempt to claim one more item from
$queue_instance using the same lease_time to detect remaining work (use
claimItem), and if that call returns an item then cleanup that claim
(deleteItem) and throw the RuntimeException; if no item is returned, do not
throw. This change touches the $processed/$limit check and uses
$queue_instance->claimItem($lease_time) and $queue_instance->deleteItem($item)
to determine and clean up remaining items.
---
Duplicate comments:
In `@src/Drupal/QueueTrait.php`:
- Around line 180-183: The helper method queueTrackName uses a snake_case
parameter $queue_name which triggers a CamelCaseParameterName warning; rename
the parameter to $queueName in the queueTrackName method signature and update
all internal references in that method (and any callers) to use $queueName so
the code compiles and the $this->queueNames usage remains unchanged.
- Around line 57-61: When calling the plugin manager's createInstance($queue) in
queueProcessItems, wrap the createInstance(...) call in a try/catch and throw a
clear RuntimeException that includes the queue name (e.g. "No queue worker
defined for queue '{$queue}'") instead of letting the low-level plugin exception
bubble; catch the specific plugin exception if available (or \Exception) and
rethrow a \RuntimeException with the queue name and the caught exception as
previous. Apply the same guard to the other createInstance($queue) usage in this
trait so both places produce an actionable error mentioning the queue ID.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: d1725a58-bebc-4d69-b8e6-cb0a380aba1c
📒 Files selected for processing (1)
src/Drupal/QueueTrait.php
| $processed = 0; | ||
| while ($processed < $limit) { | ||
| /** @var \stdClass|false $item */ | ||
| $item = $queue_instance->claimItem($lease_time); | ||
| if (!$item) { | ||
| break; | ||
| } | ||
| $worker->processItem($item->data); | ||
| $queue_instance->deleteItem($item); | ||
| $processed++; | ||
| } | ||
|
|
||
| if ($processed >= $limit) { | ||
| throw new \RuntimeException(sprintf('Queue "%s" processing reached the safety limit of %d items.', $queue, $limit)); | ||
| } |
There was a problem hiding this comment.
Safety-limit check can fail valid “process all” runs at the exact limit.
Line 103 throws whenever $processed >= $limit. If the queue contains exactly $limit items and is fully drained, this still throws incorrectly. Only fail when the limit is reached and items remain.
♻️ Suggested fix
- if ($processed >= $limit) {
+ if ($processed === $limit && $queue_instance->numberOfItems() > 0) {
throw new \RuntimeException(sprintf('Queue "%s" processing reached the safety limit of %d items.', $queue, $limit));
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| $processed = 0; | |
| while ($processed < $limit) { | |
| /** @var \stdClass|false $item */ | |
| $item = $queue_instance->claimItem($lease_time); | |
| if (!$item) { | |
| break; | |
| } | |
| $worker->processItem($item->data); | |
| $queue_instance->deleteItem($item); | |
| $processed++; | |
| } | |
| if ($processed >= $limit) { | |
| throw new \RuntimeException(sprintf('Queue "%s" processing reached the safety limit of %d items.', $queue, $limit)); | |
| } | |
| $processed = 0; | |
| while ($processed < $limit) { | |
| /** `@var` \stdClass|false $item */ | |
| $item = $queue_instance->claimItem($lease_time); | |
| if (!$item) { | |
| break; | |
| } | |
| $worker->processItem($item->data); | |
| $queue_instance->deleteItem($item); | |
| $processed++; | |
| } | |
| if ($processed === $limit && $queue_instance->numberOfItems() > 0) { | |
| throw new \RuntimeException(sprintf('Queue "%s" processing reached the safety limit of %d items.', $queue, $limit)); | |
| } |
🧰 Tools
🪛 PHPMD (2.15.0)
[error] 104-104: Missing class import via use statement (line '104', column '17'). (undefined)
(MissingImport)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Drupal/QueueTrait.php` around lines 91 - 105, The safety check currently
throws whenever $processed >= $limit even if the queue was fully drained; update
the post-loop check so we only throw when the limit was hit and there are still
items remaining: after the loop, if ($processed >= $limit) attempt to claim one
more item from $queue_instance using the same lease_time to detect remaining
work (use claimItem), and if that call returns an item then cleanup that claim
(deleteItem) and throw the RuntimeException; if no item is returned, do not
throw. This change touches the $processed/$limit check and uses
$queue_instance->claimItem($lease_time) and $queue_instance->deleteItem($item)
to determine and clean up remaining items.
Closes #601
Summary
QueueTraitproviding Behat steps for managing Drupal core queues: clearing, processing items (specific count or all), and asserting item counts.@AfterScenariocleanup hook (triggered by@queuetag) to delete queues used during tests.queueGetProcessLimit()andqueueGetLeaseTime()methods for customizing processing behavior.Changes
src/Drupal/QueueTrait.php- New trait with five steps:Given the :queue queue is empty,When I process :count item(s) from the :queue queue,When I process all items from the :queue queue,Then the :queue queue should have :count item(s),Then the :queue queue should be empty.tests/behat/features/drupal_queue.feature- BDD tests covering all steps including positive assertions and negative@traitscenarios.tests/behat/bootstrap/FeatureContext.php- RegisteredQueueTrait.tests/behat/bootstrap/FeatureContextTrait.php- Added helper stepI add :count items to the :queue queuefor test setup.README.md,STEPS.md- Updated documentation.Added QueueTrait for Drupal Core Queue Management
Overview
This PR introduces a new QueueTrait that provides Behat step definitions for managing and asserting Drupal core queues during BDD tests, plus tests and documentation.
Key Changes
New trait: src/Drupal/QueueTrait.php
Tests and test helpers:
Documentation:
Misc:
Compliance with CONTRIBUTING.md (steps format)
Critical violation:
Other checks:
queue) in QueueTrait follow the documented naming conventions.