////
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements.  See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
////
= For Committers

image::business-gremlin.png[width=400]

The guidelines that follow generally apply to those with commit access to the main repository, but those seeking to
contribute will also find helpful information here on the development style and process for the project.

[[initial-setup]]
== Initial Setup

Once the Apache TinkerPop PMC has sent an invitation to a contributor to become a new committer and that contributor
has accepted that invitation and provided their iCLA to Apache, then there are some administrative steps that the
new committer can expect to go through to complete the process so that they have access to Apache resources like the
Git repository. While the information for completing the process can be found in a multitude of places the following
listing provides a summary of what to do next:

* Look for a welcome email from root_at_apache.org. This will contain your Apache user name
** e.g. "Welcome to the Apache Software Foundation (ASF)!"
* Visit https://id.apache.org/reset/enter and enter your user name
* Look for a password reset email from root_at_apache.org. This will have been sent after you confirmed your user name, above.
** e.g. "Password reset request for [username] from Apache ID"
* Visit the link provided in the password reset email, and choose a new password. ASF asks you to choose a strong one. You will see a "Password change successful" page when this is done.
* You will now have SVN access
** link:https://svn.apache.org/[svn.apache.org]
* Try sending yourself an email at your new [username]@apache.org email address. It should forward to your primary address.
* Check your account details at https://id.apache.org/details/[username]
* Link your accounts using gitbox (https://gitbox.apache.org/setup/)
** Link your Apache account
** Link your GitHub account
*** You will be asked to "Authorize Apache M.A.T.T."
** Obtain MFA status
*** Visit link:https://id.apache.org[id.apache.org] and set your GitHub ID to be invited to the org
*** Wait for an email to arrive from "support_at_github.com", and click "Join @apache". Accept the invitation on github.com. Your new MFA status will not immediately be reflected on github, but you will get a confirmation email from noreply_at_github.com. Later, the status will read "MFA ENABLED"
**** e.g. "[GitHub] @asf-gitbox has invited you to join the @apache organization"
*** You can find yourself by searching in the link:https://github.com/orgs/apache/teams/apache-committers[Apache Committers listing]
* Read the link:https://www.apache.org/dev/new-committers-guide.html[Apache Committer Guide] and link:http://www.apache.org/dev/committers.html[Apache Committer FAQ]
* Read through the other sections of this document - the Developer Documentation - for more details on project procedures and other administrative items.
** In particular, see <<rtc,Review then Commit>>
* If you have trouble committing, email dev@tinkerpop.apache.org

== Communication

TinkerPop has a link:http://groups.google.com/group/gremlin-users[user mailing list] and a
pass:[<a href="https://lists.apache.org/list.html?dev@tinkerpop.apache.org">dev mailing list</a>].  As a committer,
it is a good idea to join both.

TinkerPop also has a link:https://the-asf.slack.com/archives/CUBJ577EW[Slack channel] for more real-time communication
about the project among contributors, though it must be kept in mind that project discussion and decisions must occur
on the dev mailing list mentioned above.

Occasionally, online meetings via video conference are held. These meetings are schedule via the dev mailing list
about a week before they are to occur to find a day and time that is available for those interested in attending.
On the day of the meeting, the meeting organizer will create a Google Hangout (or similar video conferencing link).
At that point, all who are interested can attend.  Meeting minutes should be
taken and added to the <<meetings,Meetings>> section of this document using the pattern already established.

== Release Notes

There is a two-pronged approach to maintaining the change log and preparing the release notes.

1. For work that is documented in JIRA, run the release notes report to include all of
the tickets targeted for a specific release.  This report can be included in the
release announcement.

2. The manual change log (`CHANGELOG.asciidoc`) can be used to highlight large
changes, describe themes (e.g. "We focused on performance improvements") or to
give voice to undocumented changes.

Given the dependence on the JIRA report for generating additions to the `CHANGELOG.asciidoc`,
which uses the title of the issue as the line presented in the release note report, titles should
be edited prior to release to be useful in that context.  In other words, an issue title should
be understandable as a change in the fewest words possible while still conveying the gist of the
change.

Changes that break the public APIs should be marked with a "breaking" label and should be
distinguished from other changes in the release notes.

[[branches]]
== Branches

TinkerPop has several release branches:

* `3.0-dev` - 3.0.x (no longer maintained)
* `3.1-dev` - 3.1.x (no longer maintained)
* `3.2-dev` - 3.2.x (no longer maintained)
* `3.3-dev` - 3.3.x (no longer maintained)
* `3.4-dev` - 3.4.x (no longer maintained)
* `3.5-dev` - 3.5.x (non-breaking bug fixes and enhancements)
* `3.6-dev` - 3.6.x (non-breaking bug fixes and enhancements)
* `master` - 3.7.x (current development)
* `4.0-dev` - 4.0.x (future development)

The branch description above that reads "non-breaking bug fixes and enhancements" simply means that within that release
line (i.e. patch version) changes should not alter existing behavior, introduce new APIs, change serialization formats,
modify protocols, etc. In this way, users and providers have an easy way to know that within a minor release line, they
can be assured that their upgrades will not introduce potential problems. A good rule of thumb is to consider whether a
client of one version within a release line can interact properly with a server version within that same line. If so,
it is likely an acceptable change within that branch.

Changes to earlier branches should merge forward toward `master` (e.g. `3.6-dev` should merge to `master`). Please read
more about this process in the <<pull-requests, Pull Requests>> section. Note that `4.0-dev` is rebased on `master`
and currently behaves as a fresh repository as all 3.x content was removed.

As described in <<versioning,versioning>>, it is possible to do a "partial" release which will utilize a four-digit
version that starts with a "1" (e.g. `3.6.0.1`). The branching strategy for a partial release requires that a `-dev`
branch be created with the three digit prefix.

Other branches may be created for collaborating on features or for RFC's that other developers may want to inspect.
It is suggested that the JIRA issue ID be used as the prefix, since that triggers certain automation, and it provides a
way to account for the branch lifecycle, i.e. "Who's branch is this, and can I delete it?"

For branches that are NOT associated with JIRA issues, developers should utilize their Apache ID as
a branch name prefix.  This provides a unique namespace, and also a way to account for the branch lifecycle.

Developers should remove their own branches when they are no longer needed.

== Tags

Tags are used for milestones, release candidates, and approved <<versioning,partial and complete releases>>. Tags for a
complete release are simply defined by the three-digit version number. Tags for a partial release, should be prefixed
by the individual language relevant to that release. For example, if there is an initial partial release for `3.6.0`
on `gremlin-python` then the tag should be `3.6.0.1-python`.

Please refrain from creating arbitrary tags, as they produce permanent clutter.

== Issue Tracker Conventions

TinkerPop uses Apache JIRA as its link:https://issues.apache.org/jira/browse/TINKERPOP[issue tracker].  JIRA is a
very robust piece of software with many options and configurations.  To simplify usage and ensure consistency across
issues, the following conventions should be adhered to:

* An issue's "status" should generally be in one of two states: `open` or `closed` (`reopened` is equivalent to `open`
for our purposes).
** An `open` issue is newly created, under consideration or otherwise in progress.
** A `closed` issue is completed for purposes of release (i.e. code, testing, and documentation complete).
** Issues in a `resolved` state should immediately be evaluated for movement to `closed` - issue become `resolved`
by those who don't have the permissions to `close`.
* An issue's "type" should be one of two options: `bug` or `improvement`.
** A `bug` has a very specific meaning, referring to an error that prevents usage of TinkerPop AND does not have a
reasonable workaround.  Given that definition, a `bug` should generally have very high priority for a fix.
** Everything else is an `improvement` in the sense that any other work is an enhancement to the current codebase.
* The "component" should be representative of the primary area of code that it applies to and all issues should have
this property set.
* Issues are not assigned "labels" with two exceptions:
** The "breaking" label which marks an issue as one that is representative of a change in the API that might
affect users or providers.  This label is important when organizing release notes.
** The "deprecation" label which is assigned to an issue that includes changes to deprecate a portion of the API.
* The "affects/fix version(s)" fields should be appropriately set, where the "fix version" implies the version on
which that particular issue will completed. This is a field usually only set by committers.
* The "priority" field can be arbitrarily applied with one exception.  The "trivial" option should be reserved for
tasks that are "easy" for a potential new contributor to jump into and do not have significant impact to urgently
required improvements.
* The "resolution" field which is set on the close of the issue should specify the status most closely related to why
the issue was closed. In most cases, this will mean "Fixed" for a "Bug" or "Done" for an "Improvement". Only one
resolution has special meaning and care should be taken with this particular option: "Later". "Later" means that the
item is a good idea but likely will not be implemented in any foreseeable future. By closing uncompleted issues with
this resolution, it should be easy to come back to them later when needed.

== Code Style

Contributors should examine the current code base to determine what the code style patterns are and should match their
style to what is already present. Of specific note however, TinkerPop does not use "import wildcards" - IDEs should
be adjusted accordingly to not auto-wildcard the imports.

== Build Server

TinkerPop uses link:https://docs.github.com/en/actions[GitHub Actions] for link:https://en.wikipedia.org/wiki/Continuous_integration[CI]
services. The build status can be found link:https://github.com/apache/tinkerpop/actions[here]. There is a single
"build-test" workflow that runs a number of jobs that break the test execution into a series of smaller test executions.
Taken together, they provide a solid cross section of coverage of the code base

== Deprecation

When possible, committers should avoid direct "breaking" change (e.g. removing a method from a class) and favor
deprecation.  Deprecation should come with sufficient documentation and notice especially when the change involves
public APIs that might be utilized by users or implemented by providers:

* Mark the code with the `@Deprecated` annotation.
* Use javadoc to further document the change with the following content:
** `@deprecated As of release x.y.z, replaced by {@link SomeOtherClass#someNewMethod()}` - if the method is not
replaced then the comment can simply read "not replaced".  Additional comments that provide more context are
encouraged.
** `@see <a href="https://issues.apache.org/jira/browse/TINKERPOP-XXX">TINKERPOP-XXX</a>` - supply a link to the
JIRA issue for reference - the issue should include the "deprecation" label.
* Be sure that deprecated methods are still under test - consider using javadoc/comments in the tests themselves to
call out this fact.
* Create a new JIRA issue to track removal of the deprecation for future evaluation.
* Update the "upgrade documentation" to reflect the API change and how the reader should resolve it.

The JIRA issues that track removal of deprecated methods should be periodically evaluated to determine if it is
prudent to schedule them into a release.

== Developing Tests

TinkerPop has a wide variety of test types that help validate its internal code as well as external provider code.
There are "unit tests" and "integration tests". Unit tests execute on standard runs of `mvn clean install`.  These
tests tend to run quickly and provide a reasonable level of coverage and confidence in the code base.  Integration
tests are disabled by default and must be explicitly turned on with a special build property by adding
`-DskipIntegrationTests=false` to the `mvn` execution.  Integration tests run slower and may require external
components to be running when they are executed. They are "marked" as separate from unit tests by inclusion of the
suffix "IntegrateTest".

Here are some other points to consider when developing tests:

* Avoid use of `println` in tests and prefer use of a SLF4j `Logger` instance so that outputs can be controlled in a
standard way.
* If it is necessary to create files on the filesystem, do not hardcode directories - instead, use the `TestHelper` to
create directory structures.  `TestHelper` will properly create file system structure in the appropriate build
directory thus allowing proper clean-up between test runs.
* If writing tests in one of the test suites, like `gremlin-test`, it is important to remember that if a new `Graph`
instance is constructed within the test manually, that it be closed on exit of that test.  Failing to do this cleanup
can cause problems for some graph providers.
* Tests that are designed to use a `GraphProvider` implementation in conjunction with `AbstractGremlinTest` _and_ are
in the `/test` directory should not be named with `Test` as the suffix, as this will cause them to execute in some
environments without a `GraphProvider` being initialized by a suite. These types of tests should be suffixed with
`Check` instead. Please see link:https://github.com/apache/tinkerpop/blob/e32a4187e4f25e290aabe14007f9087c48a06521/neo4j-gremlin/src/test/java/org/apache/tinkerpop/gremlin/neo4j/structure/NativeNeo4jStructureCheck.java[NativeNeo4jStructureCheck]
for an example.

=== Gremlin Language Test Cases

Test cases for the Gremlin Language currently requires that the newly developed test be added in three places:

1. As a test written in Java in the `gremlin-test` module within the subpackages of
`org.apache.tinkerpop.gremlin.process.traversal.step`
2. As a test written in Gherkin in the `gremlin-test` module in the `/features` subdirectory

When writing a Java test case for a Gremlin step, be sure to use the following conventions.

* The name of the traversal generator should start with `get`, use `X` for brackets, `_` for space, and the Gremlin-Groovy sugar syntax.
** `get_g_V_hasLabelXpersonX_groupXaX_byXageX_byXsumX_name()`
* When creating a test for a step that has both a barrier and sideEffect form (e.g. `group()`, `groupCount()`, etc.), test both representations.
** `get_g_V_groupCount_byXnameX()`
** `get_g_V_groupCountXaX_byXnameX_capXaX()`
* The name of the actual test case should be the name of the traversal generator minus the `get_` prefix.
* The Gremlin-Groovy version of the test should use the sugar syntax in order to test sugar (as Gremlin-Java tests test standard syntax).
** `g.V.age.sum`
* Avoid using lambdas in the test case unless that is explicitly what is being tested as OLAP systems will typically not be able to execute those tests.
* `AbstractGremlinProcessTest` has various static methods to make writing a test case easy.
** `checkResults(Arrays.asList("marko","josh"), traversal)`
** `checkMap(new HashMap<String,Long>() {{ put("marko",1l); }}, traversal.next())`

Gherkin tests follow some important conventions and have a sub-language that must be adhered to for the tests to
function properly. Note that Gherkin tests are designed to support the testing of GLVs and at some point will likely
replace the Java tests. If a new Java test is added and an associated Gherkin tests is not, the overall build will
fail the `FeatureCoverageTest` of `gremlin-test` which validates that all tests written in Java are also implemented
in Gherkin.

The basic syntax of a Gherkin test is as follows:

[source,gherkin]
----
Scenario: g_VX1X_unionXrepeatXoutX_timesX2X__outX_name
  Given the modern graph
  And using the parameter vId1 defined as "v[marko].id"
  And the traversal of
    """
    g.V(vId1).union(__.repeat(__.out()).times(2), __.out()).values("name")
    """
  When iterated to list
  Then the result should be unordered
    | result |
    | ripple |
    | lop |
    | lop   |
    | vadas |
    | josh  |
----

==== Scenario Name

The name of the scenario needs to match the name of the Java test. If it does not then the `FeatureCoverageTest` will
fail.

==== Given

"Given" sets the context of the test. Specifically, it establishes the graph that will be used for the test. It
conforms to the pattern of "Given the _xxx_ graph" where the "xxx" may be one of the following:

* empty
* modern
* classic
* crew
* sink
* grateful

Never modify the data of any of the graphs except for the "empty" graph. The "empty" graph is the only graph that is
guaranteed to be refreshed between tests. The "empty" graph maybe be modified by the traversal under test or by an
additional "Given" option:

[source,gherkin]
----
Given the empty graph
And the graph initializer of
  """
  g.addV("person").property(T.id, 1).property("name", "marko").property("age", 29).as("marko").
    addV("person").property(T.id, 2).property("name", "vadas").property("age", 27).as("vadas").
    addV("software").property(T.id, 3).property("name", "lop").property("lang", "java").as("lop").
    addV("person").property(T.id, 4).property("name","josh").property("age", 32).as("josh").
    addV("software").property(T.id, 5).property("name", "ripple").property("lang", "java").as("ripple").
    addV("person").property(T.id, 6).property("name", "peter").property("age", 35).as('peter').
    addE("knows").from("marko").to("vadas").property(T.id, 7).property("weight", 0.5).
    addE("knows").from("marko").to("josh").property(T.id, 8).property("weight", 1.0).
    addE("created").from("marko").to("lop").property(T.id, 9).property("weight", 0.4).
    addE("created").from("josh").to("ripple").property(T.id, 10).property("weight", 1.0).
    addE("created").from("josh").to("lop").property(T.id, 11).property("weight", 0.4).
    addE("created").from("peter").to("lop").property(T.id, 12).property("weight", 0.2)
  """
----

The above configuration will use the "empty" graph and initialize it with the specified traversal. In this case, that
traversal loads the "empty" graph with the "modern" graph.

Once the graph for the test is defined, the context can be expanded to include parameters that will be applied to the
traversal under test. Any variable value being used in the traversal under test, especially ones that require a
specific type, should be defined as parameters. The structure for parameter definition looks like this:

[source,gherkin]
----
Given the modern graph
And using the parameter vId1 defined as "v[marko].id"
----

In the above example, "vId1" is the name of the parameter that will be used in the traversal. The end of that line in
quotes is the value of that parameter and should use the type system notation that has been developed for the TinkerPop
Gherkin tests. The type system notation ensures that different language variants have the ability to construct the
appropriate types expected by the tests.

The syntax of the type notation involves a prefix character to help denote the type, a value between two square
brackets, optionally suffixed with some additional notation depending on the primary type.

* Edge - *e[_xxx_]* - The "xxx" should be replaced with a representation of an edge in the form of the
`vertex_name-edgelabel->vertex_name`. This syntax may also include the `.id` suffix which would indicate getting the
edge identifier or the `.sid` suffix which gets a string representation of the edge identifier.
* Lambda - *c[_xxx_]* - The "xxx" should contain a lambda written in Groovy.
* List - *l[_xxx_,_yyy_,_zzz_,...]* - A comma separated collection of values that make up the list should be added to
between the square brackets. These values respect the type system thus allowing for creation of lists of vertices,
edges, maps, and any other available type. Spaces are taken literally, therefore it is important to avoid spaces unless
they are required for the test.
* Map - *m[_xxx_]* - The "xxx" should be replaced with a JSON string. Note that keys and values will be parsed using
the type notation system so that it is possible to have maps containing arbitrary keys and values.
* Numeric - *d[_xxx_]._y_* - The "xxx" should be replaced with a number. The suffix denoted by "y" should always be
included to further qualify the type of numeric. The following options are available:
** *d* - 32-bit Double
** *f* - 32-bit Float
** *i* - 32-bit Integer
** *l* - 64-bit Long
** *m* - Arbitrary-precision signed decimal numbers (i.e. BigDecimal in Java)
* Path - *p[_xxx_,_yyy_,_zzz_,...]* - A comma separated collection of values that make up the `Path` should be added to
between the square brackets. These values respect the type system thus allowing for creation of `Path` of vertices,
edges, maps, and any other available type.
* Set - *s[_xxx_,_yyy_,_zzz_,...]* - A comma separated collection of values that make up the set should be added to
between the square brackets. These values respect the type system thus allowing for creation of sets of vertices,
edges, maps, and any other available type.
* String - Any value not using the system notation will be interpreted as a string.
* T - *t[_xxx_]* - The "xxx" should be replaced with a value of the `T` enum, such as `id` or `label`.
* Vertex - *v[_xxx_]* - The "xxx" should be replaced with the "name" property of a vertex in the graph. This syntax may
include the `.id` suffix which would indicate getting the vertex identifier or the `.sid` suffix which gets a string
representation of the edge identifier.

In addition, parameter names should adhere to a common form as they hold some meaning to certain language variant
implementations:

* General variables of no particular type should use `xx1`, `xx2` and `xx3`.
* A `Vertex` variable should be prefixed with "v" and be followed by the `id`, therefore, `v1` would signify a `Vertex`
with the `id` of "1".
* An `Edge` variable follows the pattern of vertices but with a "e" prefix.
* The "id" of a `Vertex` or `Edge` is prefixed with "vid"`" or "eid" respectively followed by the `id`, thus, `vid1`
would be "1" and refer to the `Vertex` with that `id`.
* `Function` variables should use `l1` and `l2`.
* `Predicate` variables should use `pred1`.
* `Comparator` variables should use `c1` and `c2`.    

Finally, specify the traversal under test with the "Given" option "and the traversal":

[source,gherkin]
----
And the traversal of
  """
  g.V(vId1).union(__.repeat(__.out()).times(2), __.out()).values("name")
  """
----

It will be the results of this traversal that end up being asserted by Gherkin. When writing these test traversals,
be sure to always use the method and enum prefixes. For example, use  `__.out()` for an anonymous traversal rather
than just `out()` and prefer `Scope.local` rather than just `local`.

If a particular test cannot be written in Gherkin for some reason or cannot be otherwise supported by a GLV, first,
consider whether or not this test can be re-written in Java so that it will work for GLVs and then, second, if it
cannot, then use the following syntax for unsupported tests:

[source,gherkin]
----
Scenario: g_V_outXcreatedX_groupCountXxX_capXxX
  Given an unsupported test
  Then nothing should happen because
    """
    The result returned is not supported under GraphSON 2.x and therefore cannot be properly asserted. More
    specifically it has vertex keys which basically get toString()'d under GraphSON 2.x. This test can be supported
    with GraphSON 3.x.
    """
----

==== When

The "When" options get the result from the traversal in preparation for assertion. There are two options to iterate:

* "When iterated to list" - iterates the entire traversal into a list result that is asserted
* "When iterated next" - gets the first value from the traversal as the result to be asserted

There should be only one "When" defined in a scenario.

==== Then

The "Then" options handle the assertion of the result. There are several options to consider:

* "the result should have a count of _xxx_" - assumes a list value in the result and counts the number of values
in it
* "the result should be empty" - no results
* "the result should be ordered" - the exact results and should appear in the order presented
* "the result should be unordered" - the exact results but can appear any order
* "the result should be of" - results can be any of the specified values and in any order (use when guarantees
regarding the exact results cannot be pre-determined easily - see the `range()`-step tests for examples)

These final three types of assertions mentioned above should be followed by a Gherkin table that has one column, where
each row value in that column represents a value to assert in the result. These values are type notation respected as
shown in the following example:

[source,gherkin]
----
Then the result should be unordered
  | result |
  | ripple |
  | lop |
  | lop   |
  | vadas |
  | josh  |
----

Another method of assertion is to test mutations in the original graph. Again, mutations should only occur on the
"empty" graph, but they can be validated as follows:

[source,gherkin]
----
Scenario: g_V_outE_drop
  Given the empty graph
  And the graph initializer of
    """
    g.addV().as("a").addV().as("b").addE("knows").to("a")
    """
  And the traversal of
    """
    g.V().outE().drop()
    """
  When iterated to list
  Then the result should be empty
  And the graph should return 2 for count of "g.V()"
  And the graph should return 0 for count of "g.E()"
----

== Developing Benchmarks

Benchmarks are a useful tool to track performance between TinkerPop versions and also as tools to aid development
decision making. TinkerPop uses link:http://openjdk.java.net/projects/code-tools/jmh/[OpenJDK JMH] for benchmark development.
The JMH framework provides tools for writing robust benchmarking code that avoid many of the pitfalls inherent in benchmarking
JIT compiled code on the JVM.  Example JMH benchmarks can be found
link:http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/[here].

TinkerPop benchmarks live in the `gremlin-benchmark` module and can either be run from within your IDE or as a standalone
uber-jar.  The uber-jar is the JMH recommended approach and also makes it easy to distribute artifacts to various environments
to gather benchmarking numbers.  Having said that, in most cases it should be sufficient to run it from within the IDE.

Benchmarks will not run by default because they are time consuming.  To enable benchmarks during the test phase do
`-DskipBenchmarks=false`.  To change the number of warmup iterations, measurement iterations, and forks you can do
`mvn clean test -DskipBenchmarks=false -DdefaultForks=5 -DmeasureIterations=20 -DwarmupIterations=20`.  Benchmark results
will be output by default to the `benchmarks` directory in JSON format.

Benchmarks may also be run from the command line using the JMH runner.  Build the uber-jar and simply run
`java -jar gremlin-benchmark-TP-VERSION.jar`.  To see a list of JMH runner options, add the `-h` flag.

The JUnit/JMH integration was inspired by the Netty projects microbenchmarking suite.  Please refer to the Netty
link:http://netty.io/wiki/microbenchmarks.html[docs] for more details.  Presently there are 3 abstract benchmark classes
that may be used as building blocks for your benchmarks; `AbstractBenchmarkBase`, `AbstractGraphBenchmark`, and
`AbstractGraphMutateBenchmark`.

* `AbstractBenchmarkBase` - extend when your benchmark does not require a graph instance
* `AbstractGraphBenchmark` - extend when you are benchmarking read operations against a graph
* `AbstractGraphMutateBenchmark` - extend when you are benchmarking graph mutation operations eg. `g.addV()`, `graph.addVertex()`

[[rtc]]
== Review then Commit

Code modifications must go through a link:http://www.apache.org/foundation/glossary.html#ReviewThenCommit[review-then-commit] (RTC)
process before being merged into a release branch. All committers should follow the pattern below, where "you" refers
to the committer wanting to put code into a release branch.

* Make a JIRA ticket for the software problem you want to solve (i.e. a fix).
* Fork the release branch that the fix will be put into.
** The branch name should be the JIRA issue identifier (e.g. `TINKERPOP-XXX`).
* Develop your fix in your branch.
* When your fix is complete and ready to merge, issue a <<pull-requests,pull request>>.
** Be certain that the test suite is passing.
** If you updated documentation, be sure that the `process-docs.sh` is building the documentation correctly.
* Before you can merge your branch into the release branch, you must have at least 3 +1 link:http://www.apache.org/foundation/glossary.html#ConsensusApproval[consensus votes]
from other committers OR a single +1 from a committer and a seven day review period for objections (i.e. a "cool down
period") at which point we will assume a lazy consensus.
** Please see the Apache Software Foundations regulations regarding link:http://www.apache.org/foundation/voting.html#votes-on-code-modification[Voting on Code Modifications].
** With the "cool down" process and lazy consensus the single +1 may (should) come from the committer who submitted
the pull request (in other words, the change submitter and the reviewer are the same person).
** Committers are trusted with their changes, but are expected to request reviews for complex changes as necessary and
not rely strictly on lazy consensus.
* Votes are issued by TinkerPop committers as comments to the pull request.
* Once either consensus position is reached, you are responsible for merging to the release branch and handling any merge conflicts.
** If there is a higher version release branch that requires your fix (e.g. `3.y-1.z` fix going to a `3.y.z` release), multiple pull requests may be necessary (i.e. one for each branch).
* Be conscious of deleting your branch if it is no longer going to be used so stale branches don't pollute the repository.

NOTE: These steps also generally apply to external pull requests from those who are not official Apache committers. In
this case, the person responsible for the merge after voting is typically the first person available
who is knowledgeable in the area that the pull request affects. Any additional coordination on merging can be handled
via the pull request comment system.

For those performing reviews as part of this process it is worth noting that the notion of "review" is fairly wide for
our purposes. TinkerPop has grown into a large and complex code base and very few people (if anyone) is knowledgeable
on all of its modules. Detailed code reviews might often be difficult or impossible as a result.

To be clear, a "review" need not be specifically about the exact nature of the code. It is perfectly reasonable to
review (and VOTE) in the following fashion:

* VOTE +1 - ran docker integration tests and everything passes
* VOTE +1 - reviewed the code in detail - solid pull request
* VOTE +1 - agree with the principle of this pull request but don't fully understand the code
* VOTE +1 - read through the updated documentation and understand why this is important, nice

Non-committers are welcome to review and VOTE as well and while their VOTEs are not binding, they will be taken as
seriously as non-binding VOTEs on releases. Reviewing and VOTEing on pull requests as a non-committer is a great way
to contribute to the TinkerPop community and get a good pulse on the changes that are upcoming to the framework.

The following exceptions to the RTC (review-then-commit) model presented above are itemized below. It is up to the
committer to self-regulate as the itemization below is not complete and only hints at the types of commits that do not
require a review.

* You are responsible for a release and need to manipulate files accordingly for the release.
** `Gremlin.version()`, CHANGELOG dates, `pom.xml` version bumps, etc.
* You are doing an minor change and it is obvious that an RTC is not required (would be a pointless burden to the community).
** The fix is under the link:http://www.apache.org/foundation/glossary.html#CommitThenReview[commit-then-review] (CTR) policy and lazy consensus is sufficient, where a single -1 vote requires you to revert your changes.
** Adding a test case, fixing spelling/grammar mistakes in the documentation, fixing LICENSE/NOTICE/etc. files, fixing a minor issue in an already merged branch.

When the committer chooses CTR, it is considered good form to include something in the commit message that explains
that CTR was invoked and the reason for doing so.  For example, "Invoking CTR as this change encompasses minor
adjustments to text formatting." CTR based commits will still require manual merging through all release branches.
Merges should occur in reverse order, starting with the latest release version first (e.g. if the fix is going to
3.3.x then the change should be merged in the following order `master`, `3.4-dev`, `3.3-dev`).

[[pull-requests]]
=== Pull Requests

When submitting a pull request to one of the <<branches, release branches>>, be sure it uses the following style:

* The title of the pull request is the JIRA ticket number + "colon" + the title of the JIRA ticket.
* The first line of the pull request message should contain a link to the JIRA ticket.
* Discuss what you did to solve the problem articulated in the JIRA ticket.
* Discuss any "extra" work done that go beyond the assumed requirements of the JIRA ticket.
* Be sure to explain what you did to prove that the issue is resolved.
** Test cases written.
** Integration tests run (if required for the work accomplished).
** Documentation building (if required for the work accomplished).
** Any manual testing (though this should be embodied in a test case).
* Notes about what you will do when you merge to the respective release branch (e.g. update CHANGELOG).
** These types of "on merge tweaks" are typically done to extremely dynamic files to combat and merge conflicts.
* If you are a TinkerPop committer, you can VOTE on your own pull request, so please do so.

A pull request will typically be made to a target <<branches, branch>>. Assuming that branch is upstream of other
release branches (e.g. a pull request made to for the branch containing 3.3.x must merge to the branch that releases
3.4.x), it is important to be sure that those changes are merged to the downstream release branches. If the merge from
one release branch to another is not terribly conflicted, it is likely safe to offer a single pull request and then
merge through the release branches after review. If there is conflict or the likelihood of test failures in downstream
branches then this process is best handled by multiple pull requests: one to each release branch. Release branches with
merged changes should be pushed in reverse order, starting with the latest release version first (e.g. if the fix is
going to 3.3.x then the change should be merged in the following order: `master`, 3.4-dev`, `3.3-dev`).

As an example, consider a situation where there is a feature branch named "TINKERPOP-1234" that contains a fix for
the `3.4-dev` branch:

[source,bash]
----
`git checkout -b TINKERPOP-1234 3.4-dev`
// do a bunch of stuff to implement TINKERPOP-1234 and commit/push
git checkout -b <TINKERPOP-1234-master> master
git merge TINKERPOP-1234
----

At this point, there are two branches, with the same set of commits going to `3.4-dev` and `master`. Voting will occur
on both pull requests. After a successful vote, it is time to merge. If there are no conflicts, then simply `git merge`
both pull requests to their respective branches. If there are conflicts, then there is some added work to do - time to
rebase:

[source,bash]
----
git checkout TINKERPOP-1234
git rebase origin/3.4-dev
----

Depending on the conflict, it might be a good idea to re-test before going any further, otherwise:

[source,bash]
----
git push origin TINKERPOP-1234 --force
----

Now, `git rebase` has re-written the commit history, which makes a mess of the other pull request to master. This
problem is rectified by essentially re-issuing the PR:

[source,bash]
----
git checkout TINKERPOP-1234-master
git reset --hard origin/master
git merge TINKERPOP-1234
----

Again, depending on the changes, it may make sense to re-test at this point, otherwise:

[source,bash]
----
git push origin TINKERPOP-1234-master --force
----

It should now be safe to merge both pull requests to their release branches.

IMPORTANT: Always take a moment to review the commits in a particular pull request. Be sure that they are *all* related
to the work that was done and that no extraneous commits are present that cannot be explained. Ensuring a pull request
only contains the expected commits is the responsibility of the committer as well as the reviewer.

[[dependencies]]
== Dependencies

There are many dependencies on other open source libraries in TinkerPop modules. When adding dependencies or
altering the version of a dependency, developers must consider the implications that may apply to the TinkerPop
LICENSE and NOTICE files. There are two implications to consider:

. Does the dependency fit an Apache _approved_ license?
. Given the addition or modification to a dependency, does it mean any change for TinkerPop LICENSE and NOTICE files?

Understanding these implications is important for insuring that  TinkerPop stays compliant with the Apache 2 license
that it releases under.

Regarding the first item, refer to the Apache Legal for a list of link:http://www.apache.org/legal/resolved.html[approved licenses]
that are compatible with the Apache 2 license.

The second item requires a bit more effort to follow. The Apache website offers a
link:http://www.apache.org/dev/licensing-howto.html[how-to guide] on the approach to maintaining appropriate LICENSE
and NOTICE files, but this guide is designed to offer some more specific guidance as it pertains to TinkerPop
and its distribution.

To get started, TinkerPop has both "source" and "binary" LICENSE/NOTICE files:

* Source LICENSE/NOTICE relate to files packaged with the released source code distribution:
link:https://github.com/apache/tinkerpop/blob/master/LICENSE[LICENSE] / link:https://github.com/apache/tinkerpop/blob/master/NOTICE[NOTICE]
* Binary LICENSE/NOTICE relate to files packaged with the released binary distributions:
** Gremlin Console link:https://github.com/apache/tinkerpop/blob/master/gremlin-console/src/main/static/LICENSE[LICENSE]
/ link:https://github.com/apache/tinkerpop/blob/master/gremlin-console/src/main/static/NOTICE[NOTICE]
** Gremlin Server link:https://github.com/apache/tinkerpop/blob/master/gremlin-server/src/main/static/LICENSE[LICENSE]
/ link:https://github.com/apache/tinkerpop/blob/master/gremlin-server/src/main/static/NOTICE[NOTICE]

=== Source LICENSE and NOTICE

As dependencies are not typically added to the source distribution (i.e. the source zip distribution), there is
typically no need to edit source LICENSE/NOTICE when editing a TinkerPop `pom.xml`. These files only need to be edited
if the distribution has a file added to it.  Such a situation may arise from several scenarios, but it would most
likely come from the addition of a source file from another library.

* If the file being bundled is Apache licensed, then add an entry to NOTICE.
* If the file being bundled is under a different approved license, then add an entry to LICENSE and include a copy of
that LICENSE in the root `/licenses` directory of the code repository.

=== Binary LICENSE and NOTICE

The binary LICENSE/NOTICE is perhaps most impacted by changes to the various `pom.xml` files. After altering the
`pom.xml` file of any module, build `gremlin-driver`, Gremlin Console and Gremlin Server and examine the contents of
the binary distributions:

* target/gremlin-driver-x.y.z-uber.jar
* target/gremlin-console-x.y.z-uber.jar
* target/apache-tinkerpop-gremlin-console-x.y.z-distribution.zip
* target/apache-tinkerpop-gremlin-server-x.y.z-distribution.zip

Apache licensed software does not need to be included in LICENSE, but if the new dependency is an Apache-approved
license (e.g. BSD, MIT) then it should be added in the pattern already defined. A copy of the LICENSE should be
added to the `<project>/src/main/static/licenses` directory of the code repository and the `maven-shade-plugin` section
of the `gremlin-console` and `gremlin-driver` `pom.xml` files should be updated to reference this new license file so
that it is included in the uber jar.

To determine if changes are required to the NOTICE, first check if the bundled jar has a NOTICE file in it (typically
found in `/META-INF` directory of the jar).

* If the bundled file does not have a NOTICE, then no changes to TinkerPop's NOTICE are required.
* If the NOTICE of the file being bundled is NOT Apache licensed, then there is no change to TinkerPop's NOTICE.
* If the NOTICE of the file being bundled is Apache licensed, then include the copyright notification in TinkerPop's
NOTICE.
* If the NOTICE of the file being bundled is Apache licensed AND is an Apache Software Foundation project, then
ONLY include the portion of that NOTICE in TinkerPop's NOTICE that is unrelated to the Apache boilerplate NOTICE.
If there is no such portion that is different than the boilerplate then this NOTICE can be excluded (i.e. don't
alter TinkerPop's NOTICE at all).

Please refer to the link:http://www.apache.org/dev/licensing-howto.html#mod-notice[Modifications to Notice] section
of the Apache "Licensing How-to" for more information.

[[documentation]]
== Documentation

The documentation for TinkerPop is stored in the git repository in `docs/src/` and are then split into several
subdirectories, each representing a "book" (or its own publishable body of work). If a new AsciiDoc file is added to
a book, then it should also be included in the `index.asciidoc` file for that book, otherwise the preprocessor will
ignore it. Likewise, if a whole new book (subdirectory) is added, it must include an `index.asciidoc` file to be
recognized by the AsciiDoc preprocessor.

Adding a book also requires a change to the root `pom.xml` file. Find the "asciidoc" Maven profile and add a new
`<execution>` to the `asciidoctor-maven-plugin` configuration. For each book in `docs/src/`, there should be a
related `<execution>` that generates the HTML from the AsciiDoc. Follows the patterns already established by
the existing `<execution>` entries, paying special attention to the pathing of the '<sourceDirectory>',
`<outputDirectory>` and `<imagesdir>`.  Note that the `<outputDirectory>` represents where the book will exist when
uploaded to the server and should preserve the directory structure in git as referenced in `<sourceDirectory>`.

Adding Gremlin code examples to any of the link:https://github.com/apache/tinkerpop/tree/master/docs/src/recipes[docs/src/recipes]
or to link:https://github.com/apache/tinkerpop/tree/master/docs/src/reference/the-traversal.asciidoc[docs/src/reference/the-traversal.asciidoc]
also has the effect of improving testing of the Gremlin language. All Gremlin found in code sections that are marked
as `[gremlin-groovy]` are tested in two ways:

1. When `mvn clean install` is executed all such Gremlin are passed through the grammar parser to ensure validity.
As the grammar parser is not a Groovy parser, the test framework attempts to filter away or ignore things it can't
possibly parse. Ideally, examples should be written in such a way as to be parsed by the grammar, but in cases where it
cannot be as such, the test suite simply needs to be modified to suitably ignore the example.
2. When the documentation is built, the code snippets are actually executed and errors will result in a failure to
build the documentation.

Please see the <<building-testing,Building and Testing>> section for more information on how to generate the
documentation.

=== Asciidoc Formatting Tips

*Use Asciidoctor*

Asciidoc may render differently with different tools. What may look proper and correct with an IDE may be different
than what is ultimately generated during the official build of the documentation which uses Asciidoctor. As a result
it's best to not rely on any other view of changes besides one generated by Asciidoctor.

*Anonymous Traversal Formatting*

The double underscore (i.e. `+__+`) does not typically render right and requires such code to be wrapped with
`pass:[`pass:[__]`]` or `pass:[`+__+`]`.

Cause: link:https://github.com/asciidoctor/asciidoctor/issues/1717[#1717],
link:https://github.com/asciidoctor/asciidoctor/issues/1066[#1066]

*Non-whitespace After Backtick*

Use double backtick if there is non-whitespace immediately following the trailing backtick. So rather than:
pass:[`ScriptInputFormat`'s], prefer pass:[``ScriptInputFormat``'s].

Original: [...] globally available for `ScriptInputFormat`'s `parse()` method

Fixed: [...] globally available for ``ScriptInputFormat``'s `parse()` method

Cause: link:https://github.com/asciidoctor/asciidoctor/issues/1514[#1514]

[[site]]
== Site

The content for the TinkerPop home page and related pages that make up the web site at link://tinkerpop.apache.org[tinkerpop.apache.org]
is stored in the git repository under `/docs/site`. In this way, it becomes easier for the community to provide content
presented there, because the content can be accepted via the standard workflow of a pull request. To generate the site
for local viewing, run `bin/generate-home.sh`, which will build the site in `target/site/`. Note that Node.js and npm
have to be installed in order for the script to work. See the <<nodejs-environment,JavaScript Environment>> section for
more info about what parts of TinkerPop depend on Node.js and npm. While most of the generated website files can be
viewed locally by opening them in a browser, some of them rely on imported resources that will be blocked by the
browser's same-origin policy if not served from a single origin using a web server. The generated website can be served
locally by running `npx serve target/site/home`. PMC members can officially publish the site with
`bin/publish-home.sh <username>`.

"Publishing" does not publish documentation (e.g. reference docs, javadocs, etc) and only publishes what is generated
from the content in `/docs/site`. Publishing the site can be performed out of band with the release cycle and is no
way tied to a version. The `master` branch should always be considered the "current" web site and publishing should
only happen from that branch.

[[logging]]
== Logging

TinkerPop uses SLF4j for logging and typically leans back on Log4j as the implementation. Configuring log outputs
for debugging purposes within tests can be altered by editing the `log4j-test.properties` file in each module's test
resources.  That file gets copied to the `target/test-classes` on build and surefire and failsafe plugins in maven
are then configured to point at that area of the file system for those configuration files. The properties files
can be edited to fine tune control of the log output, but generally speaking the current configuration is likely
best for everyone's general purposes, so if changes are made please revert them prior to commit.

[[io]]
== IO Documentation and Testing

The link:https://tinkerpop.apache.org/docs/x.y.z/dev/io[IO Documentation] provides more details into GraphML, GraphSON
and Gryo with a special focus on the needs of developers who are working directly with these formats. GraphSON gets
the greatest focus here as it is used as the primary IO format for link:https://tinkerpop.apache.org/docs/x.y.z/reference/#gremlin-drivers-variants[GLVs].
This documentation is largely generated from the `gremlin-io-test` module found under `gremlin-tools`. The
`gremlin-io-test` module also includes a testing framework which validates that formats don't break between TinkerPop
versions. Unfortunately, this module requires some maintenance to ensure that the documentation and tests both stay
updated.

The `gremlin-io-test` module contains a set of files in the test resources that are statically bound to the version in
which they were generated. Older versions should never be modified. The only time changes to these resources should be
accepted should be for the current `SNAPSHOT` version. The test resources are generated from the `Model` class which
contains the objects that will undergo serialization for purpose of testing. Note that these same objects in the
`Model` are also used to generate documentation.

To generate these test resources and documentation snippets based on the `Model`, use this Maven command:

[source,text]
----
mvn clean install -pl :gremlin-io-test -Dio
----

This command will generate two directories in the `/target` output directory of `gremlin-io-test`: `test-case-data`
and `dev-docs`. The contents of `test-case-data` represents the serialized `Model` objects that can be copied to the
test resources and the contents of the `dev-docs` contains asciidoc snippets that can be copied to the IO documentation.

Generating data files in the fashion mentioned above with Maven is only good for versions of TinkerPop on the 3.3.x
line because the `gremlin-io-test` module did not exist in 3.2.x. Of course, compatibility is still tested back to
those older versions. To generate test data from 3.2.x, there are Groovy scripts in the comments of the
`gryo.asciidoc` and `graphson.asciidoc` files that can copy/pasted to the Gremlin Console. They will generate the
batch of test files needed for `gremlin-io-test`.

When does this command need to be executed?

1. If a new object is added to the `Model` - in this case, the newly created data files should be copied to the
appropriate test resource directory for the current `SNAPSHOT` version and the appropriate asciidoc snippet added to
the IO asciidocs.
2. After the release of a new TinkerPop version - in this case, a new test resource directory should be created for
the `SNAPSHOT` version and the generated `test-case-data` copied in appropriately.

The second case, does require some additional discussion. When a new version is added the following classes will need
to be updated in the following ways:

*GraphBinaryCompatibility* - Include new GraphBinary 1.0 enums for the current `SNAPSHOT`.

[source,java]
----
V1_3_4_3("3.4.3", "1.0", "v1"),
V1_3_4_4("3.4.4", "1.0", "v1");
----

*GryoCompatibility* - Include new Gryo 1.0 and 3.0 enums for the current `SNAPSHOT`.

[source,java]
----
V1D0_3_3_x("3.3.x", "1.0", "v1d0"),
V3D0_3_3_x("3.3.x", "3.0", "v3d0")
----

*GraphSONCompatibility* - Include new GraphSON enums for each of the various GraphSON configurations and versions.

[source,java]
----
V1D0_3_3_x("3.3.x", "1.0", "v1d0"),
V2D0_PARTIAL_3_3_x("3.3.x", "2.0", "v2d0-partial"),
V2D0_NO_TYPE_3_3_x("3.3.x", "2.0", "v2d0-no-types"),
V3D0_PARTIAL_3_3_x("3.3.x", "3.0", "v3d0");
----

*GraphBinaryCompatibilityTest* - Add the newly included `GraphBinaryCompatibility` enums to the test parameters being
careful to match the appropriate "mapper" to the right version.

*GryoCompatibilityTest* - Add the newly included `GryoCompatibility` enums to the test parameters being careful to
match the appropriate "mapper" to the right version.

*GraphSONUntypedCompatibilityTest* - Add the newly included GraphSON 1.0 and 2.0 "untyped" enums to the test parameters
being careful to match the appropriate "mapper" to the right version.

*GraphSONTypedCompatibilityTest* - Add the newly included GraphSON 3.0 and 2.0 "typed" enums to the test parameters
being careful to match the appropriate "mapper" to the right version.

At this point, all of the IO tests are rigged up properly and assuming the test resources are available a standard
`mvn clean install` should execute the compatibility tests and validate that everything is working as expected and
that there are no breaks in serialization processes.
