All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tinkerpop.gremlin.process.computer.GraphComputerTest Maven / Gradle / Ivy

The newest version!
package com.tinkerpop.gremlin.process.computer;

import com.tinkerpop.gremlin.ExceptionCoverage;
import com.tinkerpop.gremlin.LoadGraphWith;
import com.tinkerpop.gremlin.process.AbstractGremlinProcessTest;
import com.tinkerpop.gremlin.process.computer.lambda.LambdaMapReduce;
import com.tinkerpop.gremlin.process.computer.lambda.LambdaVertexProgram;
import com.tinkerpop.gremlin.structure.Graph;
import com.tinkerpop.gremlin.structure.util.StringFactory;
import com.tinkerpop.gremlin.util.StreamFactory;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.Future;

import static com.tinkerpop.gremlin.LoadGraphWith.GraphData.MODERN;
import static org.junit.Assert.*;

/**
 * @author Marko A. Rodriguez (http://markorodriguez.com)
 */
@ExceptionCoverage(exceptionClass = GraphComputer.Exceptions.class, methods = {
        "providedKeyIsNotAMemoryComputeKey",
        "computerHasNoVertexProgramNorMapReducers",
        "computerHasAlreadyBeenSubmittedAVertexProgram",
        "providedKeyIsNotAnElementComputeKey",
        "isolationNotSupported"
})
@ExceptionCoverage(exceptionClass = Graph.Exceptions.class, methods = {
        "graphDoesNotSupportProvidedGraphComputer",
        "onlyOneOrNoGraphComputerClass"
})
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public abstract class GraphComputerTest extends AbstractGremlinProcessTest {

    public abstract GraphComputer get_g_compute();

    public abstract GraphComputer get_g_compute_setupXX_executeXX_terminateXtrueX();

    public abstract GraphComputer get_g_compute_setupXX_executeXX_terminateXtrueX_memoryKeysXset_incr_and_orX();

    public abstract GraphComputer get_g_compute_setupXX_executeXX_terminateXtrueX_memoryKeysXnullX();

    public abstract GraphComputer get_g_compute_setupXX_executeXX_terminateXtrueX_memoryKeysX_X();

    public abstract GraphComputer get_g_compute_setupXsetXa_trueXX_executeXX_terminateXtrueX();

    public abstract GraphComputer get_g_compute_setupXX_executeXv_blah_m_incrX_terminateX1X_elementKeysXnameLengthCounterX_memoryKeysXa_bX();

    public abstract GraphComputer get_g_compute_setupXabcdeX_executeXtestMemoryX_terminateXtestMemoryXmemoryKeysXabcdeX();

    public abstract GraphComputer get_g_compute_mapXageX_reduceXsumX_memoryXnextX_memoryKeyXageSumX();

    public abstract GraphComputer get_g_compute_executeXcounterX_terminateX8X_mapreduceXcounter_aX_mapreduceXcounter_bX();

    public abstract GraphComputer get_g_compute_mapXidX_reduceXidX_reduceKeySortXreverseX_memoryKeyXidsX();

    @Test
    @LoadGraphWith(MODERN)
    public void shouldHaveStandardStringRepresentation() {
        final GraphComputer computer = get_g_compute();
        assertEquals(StringFactory.graphComputerString(computer), computer.toString());
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldNotAllowWithNoVertexProgramNorMapReducers() throws Exception {
        try {
            get_g_compute().submit().get();
            fail("Should throw an IllegalStateException when there is no vertex program nor map reducers");
        } catch (Exception ex) {
            validateException(GraphComputer.Exceptions.computerHasNoVertexProgramNorMapReducers(), ex);
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldFailIfIsolationIsNotSupported() {
        final GraphComputer computer = get_g_compute();
        if (!computer.features().supportsIsolation(GraphComputer.Isolation.BSP)) {
            try {
                computer.isolation(GraphComputer.Isolation.BSP);
                fail("GraphComputer.isolation() should throw an exception if the isolation is not supported");
            } catch (Exception ex) {
                validateException(GraphComputer.Exceptions.isolationNotSupported(GraphComputer.Isolation.BSP), ex);
            }
        }
        if (!computer.features().supportsIsolation(GraphComputer.Isolation.DIRTY_BSP)) {
            try {
                computer.isolation(GraphComputer.Isolation.DIRTY_BSP);
                fail("GraphComputer.isolation() should throw an exception if the isolation is not supported");
            } catch (Exception ex) {
                validateException(GraphComputer.Exceptions.isolationNotSupported(GraphComputer.Isolation.DIRTY_BSP), ex);
            }
        }
        assertEquals(StringFactory.graphComputerString(computer), computer.toString());
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldNotAllowBadGraphComputers() {
        try {
            g.compute(BadGraphComputer.class);
            fail("Providing a bad graph computer class should fail");
        } catch (Exception ex) {
            validateException(Graph.Exceptions.graphDoesNotSupportProvidedGraphComputer(BadGraphComputer.class), ex);
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldHaveImmutableComputeResultMemory() throws Exception {
        final ComputerResult results = this.get_g_compute_setupXX_executeXX_terminateXtrueX_memoryKeysXset_incr_and_orX().submit().get();

        try {
            results.memory().set("set", "test");
        } catch (Exception ex) {
            validateException(Memory.Exceptions.memoryIsCurrentlyImmutable(), ex);
        }

        try {
            results.memory().incr("incr", 1);
        } catch (Exception ex) {
            validateException(Memory.Exceptions.memoryIsCurrentlyImmutable(), ex);
        }

        try {
            results.memory().and("and", true);
        } catch (Exception ex) {
            validateException(Memory.Exceptions.memoryIsCurrentlyImmutable(), ex);
        }

        try {
            results.memory().or("or", false);
        } catch (Exception ex) {
            validateException(Memory.Exceptions.memoryIsCurrentlyImmutable(), ex);
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldNotAllowNullMemoryKeys() throws Exception {
        try {
            get_g_compute_setupXX_executeXX_terminateXtrueX_memoryKeysXnullX().submit().get();
            fail("Providing null memory key should fail");
        } catch (Exception ex) {
            // TODO ex.printStackTrace();
            //validateException(Memory.Exceptions.memoryKeyCanNotBeNull(), ex);
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldNotAllowEmptyMemoryKeys() throws Exception {
        try {
            get_g_compute_setupXX_executeXX_terminateXtrueX_memoryKeysX_X().submit().get();
            fail("Providing empty memory key should fail");
        } catch (Exception ex) {
            validateException(Memory.Exceptions.memoryKeyCanNotBeEmpty(), ex);
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldNotAllowSettingUndeclaredMemoryKeys() throws Exception {
        try {
            get_g_compute_setupXsetXa_trueXX_executeXX_terminateXtrueX().submit().get();
            fail("Setting a memory key that wasn't declared should fail");
        } catch (Exception ex) {
            // TODO: validateException(GraphComputer.Exceptions.providedKeyIsNotAMemoryComputeKey("a"), ex.getCause());
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldOnlyAllowOneOrNoGraphComputerClass() throws Exception {
        try {
            g.compute(BadGraphComputer.class, BadGraphComputer.class).submit().get();
            fail("Should throw an IllegalArgument when two graph computers are passed in");
        } catch (Exception ex) {
            final Exception expectedException = Graph.Exceptions.onlyOneOrNoGraphComputerClass();
            assertEquals(expectedException.getClass(), ex.getClass());
            assertEquals(expectedException.getMessage(), ex.getMessage());
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldNotAllowTheSameComputerToExecutedTwice() throws Exception {
        final GraphComputer computer = get_g_compute_setupXX_executeXX_terminateXtrueX();
        computer.submit().get(); // this should work as its the first run of the graph computer

        try {
            computer.submit(); // this should fail as the computer has already been executed
            fail("Using the same graph computer to compute again should not be possible");
        } catch (IllegalStateException e) {

        } catch (Exception e) {
            fail("Should yield an illegal state exception for graph computer being executed twice");
        }

        // test no rerun of graph computer
        try {
            computer.submit(); // this should fail as the computer has already been executed even through new program submitted
            fail("Using the same graph computer to compute again should not be possible");
        } catch (IllegalStateException e) {

        } catch (Exception e) {
            fail("Should yield an illegal state exception for graph computer being executed twice");
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldHaveConsistentMemoryVertexPropertiesAndExceptions() throws Exception {
        ComputerResult results = get_g_compute_setupXX_executeXv_blah_m_incrX_terminateX1X_elementKeysXnameLengthCounterX_memoryKeysXa_bX().submit().get();
        assertEquals(1, results.memory().getIteration());
        assertEquals(2, results.memory().asMap().size());
        assertEquals(2, results.memory().keys().size());
        assertTrue(results.memory().keys().contains("a"));
        assertTrue(results.memory().keys().contains("b"));
        assertTrue(results.memory().getRuntime() >= 0);

        assertEquals(Long.valueOf(12), results.memory().get("a"));   // 2 iterations
        assertEquals(Long.valueOf(28), results.memory().get("b"));
        try {
            results.memory().get("BAD");
            fail("Should throw an IllegalArgumentException");
        } catch (IllegalArgumentException e) {
            assertEquals(Memory.Exceptions.memoryDoesNotExist("BAD").getMessage(), e.getMessage());
        }
        assertEquals(Long.valueOf(6), results.graph().V().count().next());

        results.graph().V().forEachRemaining(v -> {
            assertTrue(v.property("nameLengthCounter").isPresent());
            assertEquals(Integer.valueOf(v.value("name").length() * 2), Integer.valueOf(v.value("nameLengthCounter")));
        });
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldAndOrIncrCorrectlyThroughSubStages() throws Exception {
        ComputerResult results = get_g_compute_setupXabcdeX_executeXtestMemoryX_terminateXtestMemoryXmemoryKeysXabcdeX().submit().get();
        assertEquals(2, results.memory().getIteration());
        assertEquals(5, results.memory().asMap().size());
        assertEquals(5, results.memory().keys().size());
        assertTrue(results.memory().keys().contains("a"));
        assertTrue(results.memory().keys().contains("b"));
        assertTrue(results.memory().keys().contains("c"));
        assertTrue(results.memory().keys().contains("d"));
        assertTrue(results.memory().keys().contains("e"));

        assertEquals(Long.valueOf(18), results.memory().get("a"));
        assertEquals(Long.valueOf(0), results.memory().get("b"));
        assertFalse(results.memory().get("c"));
        assertTrue(results.memory().get("d"));
        assertTrue(results.memory().get("e"));
    }


    @Test
    @LoadGraphWith(MODERN)
    public void shouldAllowMapReduceWithNoVertexProgram() throws Exception {
        final ComputerResult results = get_g_compute_mapXageX_reduceXsumX_memoryXnextX_memoryKeyXageSumX().submit().get();
        assertEquals(123, results.memory().get("ageSum").intValue());
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldSupportMultipleMapReduceJobs() throws Exception {
        final ComputerResult results = get_g_compute_executeXcounterX_terminateX8X_mapreduceXcounter_aX_mapreduceXcounter_bX().submit().get();
        assertEquals(60, results.memory().get("a").intValue());
        assertEquals(1, results.memory().get("b").intValue());
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldSortReduceOutput() throws Exception {
        final ComputerResult results = get_g_compute_mapXidX_reduceXidX_reduceKeySortXreverseX_memoryKeyXidsX().submit().get();
        final List ids = results.memory().get("ids");
        assertEquals(6, ids.size());
        for (int i = 1; i < ids.size(); i++) {
            assertTrue(ids.get(i) < ids.get(i - 1));
        }
    }


    public static class ComputerTest extends GraphComputerTest {

        public ComputerTest() {
            requiresGraphComputer = true;
        }

        @Override
        public GraphComputer get_g_compute() {
            return g.compute();
        }

        @Override
        public GraphComputer get_g_compute_setupXX_executeXX_terminateXtrueX() {
            return g.compute().program(LambdaVertexProgram.build().create());
        }

        @Override
        public GraphComputer get_g_compute_setupXX_executeXX_terminateXtrueX_memoryKeysXset_incr_and_orX() {
            return g.compute().program(LambdaVertexProgram.build().memoryComputeKeys("set", "incr", "and", "or").create());
        }

        @Override
        public GraphComputer get_g_compute_setupXX_executeXX_terminateXtrueX_memoryKeysXnullX() {
            return g.compute().program(LambdaVertexProgram.build().memoryComputeKeys(new HashSet() {{
                add(null);
            }}).create());
        }

        @Override
        public GraphComputer get_g_compute_setupXX_executeXX_terminateXtrueX_memoryKeysX_X() {
            return g.compute().program(LambdaVertexProgram.build().memoryComputeKeys("").create());
        }

        @Override
        public GraphComputer get_g_compute_setupXsetXa_trueXX_executeXX_terminateXtrueX() {
            return g.compute().program(LambdaVertexProgram.build().setup(m -> m.set("a", true)).create());
        }

        @Override
        public GraphComputer get_g_compute_setupXX_executeXv_blah_m_incrX_terminateX1X_elementKeysXnameLengthCounterX_memoryKeysXa_bX() {
            return g.compute().program(LambdaVertexProgram.build().
                    setup(memory -> {
                    }).
                    execute((vertex, messenger, memory) -> {
                        // TODO: Implement wrapper for GiraphGraph internal TinkerVertex
                        try {
                            vertex.property("blah", "blah");
                            fail("Should throw an IllegalArgumentException");
                        } catch (IllegalArgumentException e) {
                            assertEquals(GraphComputer.Exceptions.providedKeyIsNotAnElementComputeKey("blah").getMessage(), e.getMessage());
                        } catch (Exception e) {
                            fail("Should throw an IllegalArgumentException: " + e);
                        }

                        memory.incr("a", 1);
                        if (memory.isInitialIteration()) {
                            vertex.property("nameLengthCounter", vertex.value("name").length());
                            memory.incr("b", vertex.value("name").length());
                        } else {
                            vertex.singleProperty("nameLengthCounter", vertex.value("name").length() + vertex.value("nameLengthCounter"));
                        }
                    }).
                    terminate(memory -> memory.getIteration() == 1).
                    elementComputeKeys("nameLengthCounter").
                    memoryComputeKeys("a", "b").create());
        }

        @Override
        public GraphComputer get_g_compute_setupXabcdeX_executeXtestMemoryX_terminateXtestMemoryXmemoryKeysXabcdeX() {
            return g.compute().program(LambdaVertexProgram.build().
                    setup(memory -> {
                        memory.set("a", 0l);
                        memory.set("b", 0l);
                        memory.set("c", true);
                        memory.set("d", false);
                        memory.set("e", true);
                    }).
                    execute((vertex, messenger, memory) -> {
                        // test current step values
                        assertEquals(Long.valueOf(6 * memory.getIteration()), memory.get("a"));
                        assertEquals(Long.valueOf(0), memory.get("b"));
                        if (memory.isInitialIteration()) {
                            assertTrue(memory.get("c"));
                            assertFalse(memory.get("d"));
                        } else {
                            assertFalse(memory.get("c"));
                            assertTrue(memory.get("d"));
                        }
                        assertTrue(memory.get("e"));

                        // update current step values and make sure returns are correct
                        assertEquals(Long.valueOf(6 * memory.getIteration()) + 1l, memory.incr("a", 1l));
                        assertEquals(Long.valueOf(0) + 1l, memory.incr("b", 1l));
                        assertFalse(memory.and("c", false));
                        assertTrue(memory.or("d", true));
                        assertFalse(memory.and("e", false));

                        // test current step values, should be the same as previous prior to update
                        assertEquals(Long.valueOf(6 * memory.getIteration()), memory.get("a"));
                        assertEquals(Long.valueOf(0), memory.get("b"));
                        if (memory.isInitialIteration()) {
                            assertTrue(memory.get("c"));
                            assertFalse(memory.get("d"));
                        } else {
                            assertFalse(memory.get("c"));
                            assertTrue(memory.get("d"));
                        }
                        assertTrue(memory.get("e"));
                    }).
                    terminate(memory -> {
                        assertEquals(Long.valueOf(6 * (memory.getIteration() + 1)), memory.get("a"));
                        assertEquals(Long.valueOf(6), memory.get("b"));
                        assertFalse(memory.get("c"));
                        assertTrue(memory.get("d"));
                        assertFalse(memory.get("e"));
                        memory.set("b", 0l);
                        memory.set("e", true);
                        return memory.getIteration() > 1;
                    }).
                    memoryComputeKeys("a", "b", "c", "d", "e").create());
        }

        @Override
        public GraphComputer get_g_compute_mapXageX_reduceXsumX_memoryXnextX_memoryKeyXageSumX() {
            return g.compute().mapReduce(LambdaMapReduce.build()
                    .map((v, e) -> v.property("age").ifPresent(age -> e.emit(MapReduce.NullObject.instance(), age)))
                    .reduce((k, vv, e) -> e.emit(StreamFactory.stream(vv).mapToInt(i -> i).sum()))
                    .memory(i -> i.next().getValue())
                    .memoryKey("ageSum").create());
        }

        @Override
        public GraphComputer get_g_compute_executeXcounterX_terminateX8X_mapreduceXcounter_aX_mapreduceXcounter_bX() {
            return g.compute().program(LambdaVertexProgram.build()
                    .execute((vertex, messenger, memory) -> {
                        vertex.singleProperty("counter", memory.isInitialIteration() ? 1 : vertex.value("counter") + 1);
                    })
                    .terminate(memory -> memory.getIteration() > 8)
                    .elementComputeKeys(new HashSet<>(Arrays.asList("counter"))).create())
                    .mapReduce(LambdaMapReduce.build()
                            .map((v, e) -> e.emit(v.value("counter")))
                            .reduce((k, vv, e) -> {
                                int counter = 0;
                                while (vv.hasNext()) {
                                    counter = counter + vv.next();
                                }
                                e.emit(MapReduce.NullObject.instance(), counter);

                            })
                            .memory(i -> i.next().getValue())
                            .memoryKey("a").create())
                    .mapReduce(LambdaMapReduce.build()
                            .map((v, e) -> e.emit(MapReduce.NullObject.instance(), v.value("counter")))
                            .combine((k, vv, e) -> e.emit(1))
                            .reduce((k, vv, e) -> e.emit(1))
                            .memory(i -> i.next().getValue())
                            .memoryKey("b").create());

        }

        @Override
        public GraphComputer get_g_compute_mapXidX_reduceXidX_reduceKeySortXreverseX_memoryKeyXidsX() {
            return g.compute().mapReduce(LambdaMapReduce.>build()
                    .map((vertex, emitter) -> emitter.emit(Long.valueOf(vertex.id().toString()), Long.valueOf(vertex.id().toString())))
                    .reduce((key, values, emitter) -> values.forEachRemaining(id -> emitter.emit(id, id)))
                    .memoryKey("ids")
                    .reduceKeySort(Comparator::reverseOrder)
                    .memory(itty -> {
                        final List list = new ArrayList<>();
                        itty.forEachRemaining(id -> list.add(id.getKey()));
                        return list;
                    })
                    .create());
        }

    }




   /*
    @Test
    @LoadGraphWith(MODERN)
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_COMPUTER)
    public void shouldSupportVertexProperties() throws Exception {
        GraphComputer computer = g.compute();
        ComputerResult results = computer.program(LambdaVertexProgram.build().
                execute((vertex, messenger, memory) -> {

                    if (memory.getIteration() == 0) {
                        assertEquals(VertexProperty.empty(), vertex.property("a"));
                        assertEquals(VertexProperty.empty(), vertex.property("b"));
                        assertFalse(vertex.property("a").isPresent());
                        assertFalse(vertex.property("b").isPresent());
                    } else if (memory.getIteration() == 1) {
                        assertEquals(1, vertex.property("a").value());
                        assertEquals(1, vertex.property("b").value());
                    } else if (memory.getIteration() == 2) {
                        assertEquals(2, vertex.properties("a").count().next().intValue());
                        assertEquals(1, vertex.properties("a").has(T.value, 1).count().next().intValue());
                        assertEquals(1, vertex.properties("a").has(T.value, 2).count().next().intValue());
                        assertEquals(2, vertex.property("b").value());
                    } else if (memory.getIteration() == 3) {
                        assertEquals(3, vertex.properties("a").count().next().intValue());
                        assertEquals(1, vertex.properties("a").has(T.value, 1).count().next().intValue());
                        assertEquals(2, vertex.properties("a").has(T.value, 2).count().next().intValue());
                        assertEquals(2, vertex.property("b").value());
                    } else {
                        fail("There should not be more than 3 iterations in this vertex program");
                    }

                    ///////////////////////////


                    if (memory.isInitialIteration()) {
                        vertex.property("a", 1);
                        vertex.property("b", 1);
                    } else {
                        vertex.property("a", 2);
                        vertex.singleProperty("b", 2);
                        try {
                            vertex.property("a");
                            fail("Should fail with IllegalStateException");
                        } catch (IllegalStateException e) {
                            assertEquals(Vertex.Exceptions.multiplePropertiesExistForProvidedKey("a").getMessage(), e.getMessage());
                        } catch (Exception e) {
                            fail("Should fail with IllegalStateException");
                        }
                        vertex.property("b");
                    }


                    ///////////////////////////

                    if (memory.getIteration() == 0) {
                        assertEquals(1, vertex.properties("a").count().next().intValue());
                        assertEquals(1, vertex.property("a").value());
                        assertEquals(1, vertex.property("b").value());
                    } else if (memory.getIteration() == 1) {
                        assertEquals(2, vertex.properties("a").count().next().intValue());
                        assertEquals(1, vertex.properties("a").has(T.value, 1).count().next().intValue());
                        assertEquals(1, vertex.properties("a").has(T.value, 2).count().next().intValue());
                        assertEquals(2, vertex.property("b").value());
                    } else if (memory.getIteration() == 2) {
                        assertEquals(3, vertex.properties("a").count().next().intValue());
                        assertEquals(1, vertex.properties("a").has(T.value, 1).count().next().intValue());
                        assertEquals(2, vertex.properties("a").has(T.value, 2).count().next().intValue());
                        assertEquals(2, vertex.property("b").value());
                    } else if (memory.getIteration() == 3) {
                        assertEquals(4, vertex.properties("a").count().next().intValue());
                        assertEquals(1, vertex.properties("a").has(T.value, 1).count().next().intValue());
                        assertEquals(3, vertex.properties("a").has(T.value, 2).count().next().intValue());
                        assertEquals(2, vertex.property("b").value());
                    } else {
                        fail("There should not be more than 3 iterators in this vertex program");
                    }

                }).
                terminate(memory -> memory.getIteration() > 2).
                elementComputeKeys(new HashSet<>(Arrays.asList("a", "b"))).create())
                .submit().get();
        assertEquals(3, results.getMemory().getIteration());
        final Vertex vertex = results.getGraph().V().next();
        assertEquals(2, vertex.property("b").value());
        assertEquals(4, vertex.properties("a").count().next().intValue());
        assertEquals(1, vertex.properties("a").has(T.value, 1).count().next().intValue());
        assertEquals(3, vertex.properties("a").has(T.value, 2).count().next().intValue());
        assertEquals(1, vertex.properties("b").count().next().intValue());
    }*/

    class BadGraphComputer implements GraphComputer {
        @Override
        public GraphComputer isolation(final Isolation isolation) {
            return null;
        }

        @Override
        public GraphComputer program(final VertexProgram vertexProgram) {
            return null;
        }

        @Override
        public GraphComputer mapReduce(final MapReduce mapReduce) {
            return null;
        }

        @Override
        public Future submit() {
            return null;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy