Skip to the content.

Contributing to PureEdgeSim

PureEdgeSim is a research simulator, and most contributions come from people who use it to run experiments: a new orchestration algorithm, a mobility model that matches a real campus, a fix for a metric that was counted twice. This guide explains how to make such a contribution without breaking the behavior that other researchers depend on.

You do not need to be a software engineer to contribute. You do need to verify what the code actually does before documenting or changing it, and you need to leave the test suite and the changelog in a state that the next person can trust.

Where things live

Path Contents
PureEdgeSim/main/java/com/mechalikh/pureedgesim/ Framework source, one package per module.
PureEdgeSim/examples/ExampleX/ One folder per runnable scenario: its Java sources, README.md, settings/, and output/. Not part of the published Javadoc.
PureEdgeSim/test/java/com/mechalikh/pureedgesim/ JUnit 5 tests, mirroring the source packages.
docs/ This manual. Start at docs/README.md.
PureEdgeSim/settings/ Default configuration files.
PureEdgeSim/output/ Default output folder, created at run time and ignored by git.
libs/ Bundled third-party jars (jFuzzyLogic for the optional examples).
resources/ Images used by the README and the documentation.
.github/workflows/ Continuous integration: tests, coverage, and Javadoc.
CHANGELOG.md Every user-visible change, newest first.
pom.xml Build, profiles, dependencies, publishing.

The package layout and module responsibilities are described in Architecture.

Development environment

You need:

Verify the setup by compiling and running the test suite:

mvn clean test

A first run downloads dependencies and can take a few minutes. Later runs are faster.

Building and running

Compile only:

mvn clean compile

Run an example from an IDE by running its main method. From the command line, the examples are plain classes with a main method, for example:

mvn -q exec:java -Dexec.mainClass=examples.Example1

The exec plugin is not configured in the POM, so Maven downloads it on first use. Running examples from the IDE is the path used by most contributors. See Getting started for the expected output layout.

Build the library JAR without tests:

mvn -DskipTests package

The test suite

Tests use JUnit 5. They are organized in two layers:

The POM defines three profiles:

Command What runs
mvn clean test -Punit-tests Unit tests only. Integration-tagged tests are excluded. Use this when measuring unit coverage.
mvn clean test -Pintegration-tests Unit and integration tests, plus anything in the integrationtests package.
mvn clean test The default profile. Excludes the integrationtests package but does not filter by tag, so tagged tests outside that package still run. Use a named profile when you need a precise subset.

Run a single test class or method:

mvn test -Dtest=DefaultOrchestratorTest
mvn test -Dtest=DefaultOrchestratorTest#testRoundRobin

Run integration tests only:

mvn test -Dtest=SimulationIntegrationTest

Test failures fail the build

Surefire runs the suite with the default failure handling: a failing test makes mvn test exit with a non-zero status, and CI goes red. Fix the failure before merging; do not add -Dmaven.test.failure.ignore=true to a commit or workflow.

The console summary (Tests run: ..., Failures: ..., Errors: ..., Skipped: ...) and the per-class reports in target/surefire-reports/ remain the fastest way to find what broke.

Coverage

JaCoCo runs automatically during test and writes:

Package folders under target/site/jacoco/ show line, branch, and instruction coverage per class. The examples package and anything under libs are excluded from measurement.

Coverage is a diagnostic, not a target to game. A getter with no test is less important than an untested branch in failure accounting. When you add a model with branches (battery limits, routing cases, retry paths), add a test for each branch you expect experiments to hit.

Writing tests

Follow the existing patterns:

If a test requires a specific fixture, put it under PureEdgeSim/test/resources/ and load it through the class loader so it works from IDEs and Maven alike.

Code conventions

The framework is small and deliberately dependency-light. Match the surrounding code:

Formatting: use tabs, as the existing sources do. Keep lines readable; the project does not enforce a formatter, so do not reformat whole files in a functional pull request.

Javadoc

Public classes and methods in PureEdgeSim/main/java should have Javadoc that states the contract, not a restatement of the signature.

Write:

/**
 * Returns the number of tasks that have finished execution on this node.
 * Tasks that failed before execution are not included.
 *
 * @return completed task count, never negative
 */
public int getTasksCompleted() {

Avoid:

/**
 * Gets the tasks completed.
 *
 * @return the tasks completed
 */

Rules that the Javadoc tool enforces (and that contributors should run before submitting):

Build the Javadoc locally:

mvn javadoc:javadoc

Output goes to target/site/apidocs/index.html. The build is configured with failOnError=false, which means a green build does not prove the Javadoc is clean. Read the warnings. The release build reports no errors and one warning, about the JDK 1.8 API link running against a modular JDK; treat any new warning as a regression. The javadoc workflow runs the same goal on every push and pull request and uploads the generated site as a downloadable artifact; it does not publish to GitHub Pages.

Publishing the documentation website

GitHub Pages builds the website from the root of the master branch with Jekyll. _config.yml selects the Cayman theme and enables relative links and the sitemap, so README.md becomes the home page, docs/README.md becomes /docs/, and links between markdown files are rewritten to their .html pages. Add a guide as a plain markdown file in docs/, give it a description: front matter line, link it from docs/README.md, push, and it is published. The description is used for the page meta description and social card; GitHub shows it as a one-row table at the top of the file, so keep it to that single key. docs/README.md additionally carries permalink: /docs/ because Jekyll’s README index only applies to files without front matter; keep it so the manual stays at /docs/. _config.yml also sets kramdown.parse_block_html: true, so fenced blocks nested inside <details> sections stay rendered and highlighted; keep that line if you change the Kramdown options. The two JaCoCo badge SVGs live in .github/badges/, a dot directory the Pages build refuses to publish even when _config.yml lists it under include: (measured on 2026-10-27: /PureEdgeSim/.github/badges/jacoco.svg still answered 404 with that line in place), so the README points at absolute raw.githubusercontent.com URLs instead. Those render on GitHub and on the site, and always show the badge of the current master; keep them absolute. The _includes/head-custom.html partial renders fenced mermaid blocks and builds the documentation sidebar, which opens on every page under /docs/ and on the project home page; when you add a guide, add its slug and label to the pages array in that file. Avoid Jekyll’s filename denylist for pages (README, LICENSE, CONTRIBUTING, CODE_OF_CONDUCT, ISSUE_TEMPLATE, PULL_REQUEST_TEMPLATE): those files are copied but not rendered.

Publishing a release to Maven Central

Releases go to the Central Publisher Portal at central.sonatype.com. It replaced the OSSRH staging repository at s01.oss.sonatype.org, which was retired on 2025-06-30, so the pom no longer carries a distributionManagement block and no longer needs one.

The pom publishes under the group id io.github.charafeddinemechalikh, because Central validates that every coordinate in a bundle starts with a namespace the uploading account has verified. The older group id com.mechalikh was granted in 2021 against the domain mechalikh.com. A domain namespace is claimed by publishing a DNS TXT record on that domain, so once the domain expires it cannot be re-verified: uploading a com.mechalikh bundle to the Portal on 2026-10-28 passed the pom, signature, sources and Javadoc checks and was rejected with Namespace 'com.mechalikh' is not allowed. The GitHub namespace io.github.<username> needs no domain, since it is verified automatically for a Portal account created with a GitHub login. Only the pom group id is compared against the namespaces, not the contents of the jars, so the Java packages stay com.mechalikh.pureedgesim and existing imports keep working; releases up to 5.1.0 remain downloadable under com.mechalikh.

Three things must exist before the upload can succeed:

  1. A GPG private key whose public part is reachable on a key server (gpg --send-keys <fingerprint>). The Portal verifies every signature against that public key.
  2. The fingerprint in gpg.keyname, and the key passphrase available to Maven. Passing it through the environment is what worked here; the settings.xml lookup by passphraseServerId left the plugin to call gpg with --pinentry-mode error, which fails with No pinentry.
  3. A Portal user token, generated once in the web interface (Account, Token Generator) for the account that owns the io.github.charafeddinemechalikh namespace, stored in ~/.m2/settings.xml under a server with the id central.

With those in place:

export MAVEN_GPG_PASSPHRASE="$(cat ~/.gnupg/release_pw.txt)"
mvn -B -Prelease clean deploy -DskipTests -Dgpg.keyname=<fingerprint>

The release profile runs at the verify phase, so clean deploy reaches it; package alone builds the jars but never signs them, which is easy to mistake for a signed bundle. It attaches the sources and Javadoc jars, signs four artifacts (jar, sources, javadoc, pom), then central-publishing-maven-plugin packs them under target/central-publishing/central-bundle.zip and uploads it. <autoPublish>true</autoPublish> means a bundle that passes validation is released without a click in the Portal.

Without a token the same command still leaves a publishable bundle, because the upload is the only step that fails; measured with placeholder credentials it answers HTTP 401 and Invalid token, the build stops after two and a half minutes with -DskipTests, and target/central-publishing/central-bundle.zip stays on disk with its 24 entries. That zip can be uploaded from the Publishing area of the Portal while signed in to the account that owns the namespace, which is the only way to publish without generating a token. Upload the zip and not the jar: a lone jar is rejected with Bundle has content that does NOT have a .pom file, because the pom, the sources, the Javadoc and the four signatures are expected beside it inside the archive. A manual upload always waits for the Publish button, since <autoPublish> belongs to the plugin and not to the web form, and publishing cannot be undone, only superseded by a newer version.

Two pom details exist only because of what the Portal checks, and are worth keeping:

Documentation

The manual in docs/ is part of the project. Changes to behavior should update the relevant guide in the same change, not in a follow-up.

When writing docs:

If you add a guide, link it from docs/README.md in both the reading paths and the documentation map.

Changelog and versioning

Every user-visible change gets an entry in CHANGELOG.md, under a version heading. The entry describes the effect on users: new behavior, fixed behavior, removed behavior. Internal refactors with no observable effect do not need an entry.

Bump the version only for functional work: new features, behavior changes, bug fixes, performance changes. Documentation, comments, tests, CI maintenance, and README edits do not require a version bump. When you do bump, update these together:

  1. <version> in pom.xml.
  2. The new CHANGELOG.md heading with the date.
  3. The Maven and Gradle snippets in README.md, but only after the new version is actually published. Until then, keep the snippet on the latest published release.

The changelog format is a heading followed by bullet points:

## Version 5.6 Changelog (Month Dayth Year)

*   Added ...
*   Fixed ...
*   Changed ...

Newest version first. Keep the wording concrete enough that a reader can tell whether the change affects their experiments.

Submitting a change

A good contribution is small enough to review and complete enough to trust:

  1. Branch from master.
  2. Make the change, with tests and documentation in the same branch.
  3. Run the relevant tests (mvn clean test -Punit-tests at minimum) and read the reports.
  4. Run mvn javadoc:javadoc if you touched public API, and check for new warnings.
  5. Update CHANGELOG.md.
  6. Describe in the pull request what behavior changed and how you verified it.

Commit messages follow the recent history, an imperative sentence that states the change:

Add battery-aware orchestrator example
Fix WAN latency counted once per hop

Do not mix formatting-only changes with functional changes.

Continuous integration

Three workflows run on GitHub:

Because the coverage workflow pushes to master, always rebase before pushing your own work:

git fetch origin
git rebase origin/master
git push origin master

After pushing, verify that the remote advanced:

git fetch origin
git log --oneline origin/master -1

A local commit is not a completed push.

Roadmap

These are the planned usability improvements, in priority order. Tick an item in the same pull request that implements it, and keep the XML formats and the setter-based extension API unchanged so that existing scenarios and published results keep working.

Phase 1: trust and hygiene

Acceptance: a broken XML fails with one readable message, and every output folder can be replayed as is.

Phase 2: one command to run and inspect

Acceptance: a first-time user runs a scenario, reads the summary, and inspects the plots without editing Java.

Phase 3: sweeps and reproducibility

Acceptance: a paper-grade sweep is one command, and every figure can be traced back to a stored configuration snapshot.

Phase 4: extensibility

What is already available:

What comes next:

Acceptance: a user picks a hook, generates or copies a model, names it in the settings file, and runs it without editing framework code.

Reporting a bug

Include:

If the bug is about a metric, attach the CSV row and explain which column is wrong and what value you expected. If it is about a chart, say whether save_charts and the real-time charts were enabled and whether the run was sequential or parallel.

Experimental results that look surprising are not automatically bugs. Check Output and metrics first; several columns have non-obvious definitions, and tasks in flight at the horizon are neither successes nor failures.

Code of conduct

Participation is governed by Codeofconduct.md. Be direct about technical problems, be generous with questions, and assume good faith.

License

PureEdgeSim is released under the license in LICENSE. By contributing, you agree that your contribution is distributed under the same license.