Giter VIP home page Giter VIP logo

edgex-snap-testing's Introduction

EdgeX Snap Tests

Test scripts, Github actions, and workflows for the EdgeX Foundry snaps.

The following diagram shows the Snap Testing workflow for building and testing snaps from upstream source code:

---
title: Snap Testing Workflow
---
flowchart LR
  subgraph build [Build Job]
    builda[[Build Action]] --> Source
    --> Build[Build Snap]
    --> Snap[/Artifact<br>Snap/]
  end
  subgraph test [Test Job]
    Snap -.-> testa[[Test Action]]
    --> gotests[Go Test Suites]
    --> Logs[/Artifact<br>Logs/]
  end

The Github Workflow configurations (triggers, jobs, etc) are maintained in respective upstream source codes.
The Github Actions and testing suites are maintained in this repository.

For example, this is the workflow of the tests that run on the edgex-go project. The Github Actions used in the workflow are versioned using a major semantic versioning tag. This tag is automatically moved to the latest minor and patch releases of the tests (this repository).

This project has additional workflows such as for running the tests weekly and on local PRs.

Test locally

This section includes example command to run tests.

Useful go test flags are:

  • -v is to enable verbose output
  • -failfast makes the test stop after first failure
  • -timeout 60m extends the timeout to longer than the default 10m
  • -count 1 is to avoid Go test caching for example when testing a rebuilt snap

Run one testing suite

go test -v -failfast -count 1 ./test/suites/device-mqtt

Run all suites

go test -p 1 -timeout 60m -failfast -count 1 ./test/suites/...

Run one suite with env variables

The environment variables are defined in test/utils/env.go

Full config test:

FULL_CONFIG_TEST=true go test -v -failfast -count 1 ./test/suites/device-mqtt

Testing with a local platform snap:

LOCAL_PLATFORM_SNAP="edgexfoundry_3.1.0-dev.3_amd64.snap" \
go test -v -failfast -count 1 ./test/suites/edgexfoundry

Testing with a local service snap:

LOCAL_SERVICE_SNAP="edgex-device-mqtt_2.0.1-dev.15_amd64.snap" \
go test -v -failfast -count 1 ./test/suites/device-mqtt

Testing with local platform and service snaps:

LOCAL_PLATFORM_SNAP="edgexfoundry_3.1.0-dev.3_amd64.snap" \
LOCAL_SERVICE_SNAP="edgex-device-mqtt_2.0.1-dev.15_amd64.snap" \
go test -v -failfast -count 1 ./test/suites/device-mqtt

Test with skipping the removal of snaps during teardown:

SKIP_TEARDOWN_REMOVAL=true go test -v -failfast -count 1 ./test/suites/

Test by revision:

PLATFORM_CHANNEL=4259 go test -v -failfast -count 1 ./test/suites/edgex-no-sec

This requires developer access; see snap install -h for details.

Run only one test from a suite

go test -v ./test/suites/edgexfoundry --run=TestCommon
go test -v ./test/suites/edgex-config-provider -run=TestConfigProvider/device-virtual

Test the testing utils

go test ./test/utils -count=10

Run EdgeX Ubuntu Core tests

Refer to edgex-ubuntu-core-testing

Test using Github Actions

This project includes two Github Actions that can be used in workflows to test snaps:

  • build: Checkout code, build the snap, and upload snap as build artifact
  • test: Download the snap from build artifacts (optional) and run smoke tests

A workflow that uses both the actions from v2 branch may look as follows:

.github/workflows/snap.yml

name: Snap Testing

on:
  pull_request:
    branches: [ main ]
  # allow manual trigger
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Build and upload snap
        id: build
        uses: canonical/edgex-snap-testing/build@v2
    outputs:
      snap: ${{steps.build.outputs.snap}}

  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download and test snap
        uses: canonical/edgex-snap-testing/test@v2
        with:
          name: device-mqtt
          snap: ${{needs.build.outputs.snap}}

Testing Scripts

The testing scripts can be located in the ./test/scripts directory.

To create a token for example user:

./test/scripts/login-test-user.sh

To create a self-signed TLS certificate and replace the defaults:

./test/scripts/create-tls-certificates.sh

edgex-snap-testing's People

Contributors

farshidtz avatar monicaisher avatar renovate[bot] avatar bnevis-i avatar

Stargazers

Pei Yao-Chang avatar 김윤수 avatar  avatar

Watchers

James Cloos avatar  avatar  avatar

edgex-snap-testing's Issues

Add test for checking startup status of oneshot services

Oneshot services:

  • security-bootstrapper-redis (oneshot service to setup secure Redis)
  • security-consul-bootstrapper (oneshot service to setup secure Consul)
  • security-proxy-setup (oneshot service to setup API gateway)
  • security-secretstore-setup (oneshot service to setup Vault)

Add secrets config test to replace the TLS certificate

This issue is to add a new test suite to verify the functionality of snapped secrets-config CLI. The steps for such test can be based on the documentation: https://docs.edgexfoundry.org/3.0/getting-started/Ch-GettingStartedSnapUsers/#changing-tls-certificates

Some testing code can be borrowed from what was removed in #188

For simplicity, the TLS cert creation can be done in bash, similar to https://gist.github.com/farshidtz/8fe88373a5ef1243847282f29a06184f

Keep the ASC tests alongside the default mode in ekuiper suite

Since canonical/edgex-ekuiper-snap#34, eKuiper snap subscribes to all messages on the EdgeX message bus by default. The tests have been modified accordingly in #125, replacing the original ones which were assuming subscription to the EdgeX App Service Configurable by default.

To extend the testing coverage, the testing suite needs to add tests that configure eKuiper to subscribe to messages filtered by EdgeX App Service Configurable.

Originally posted by @farshidtz in #125 (review)

Separate refresh test suites

The refresh test requires a very different setup and teardown process because it involves upgrading from an older to the current (under test) snap. This adds complication because when refresh tests run in the middle of other tests, the current snap is removed in setup and installed again in teardown.

Testing the refresh test alone will result in an unwanted workflow:

  1. install current (global setup)
  2. remove current (setup)
  3. install old snap (setup)
  4. refresh to current
  5. remove current (cleanup)
  6. install current (cleanup)
  7. remove current (global teardown)

go test -p 1 ./test/suites/edgexfoundry -run=TestCommon/refresh -v

Upstream testing workflow

Questions:

  • Should it trigger on PR and on push (current) or just on PR?
    • Fine for now.
  • Should it allow manual trigger other than by project committers?
    • Ok for now
  • Should passing the checks be required to merge PRs or optional?
    • Discuss in TSC
  • How to remove the now obsolete snap build from the jenkins pipelines?
  • Move tests source code to upstream?
    • Maybe
  • Tests moved to source code?
    • Adds to upstream code base
    • Not always possible, e.g. when testing a 3rd party snap.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/config-provider-testing.yml
  • actions/checkout v3
  • actions/checkout v3
.github/workflows/issues-to-jira.yml
.github/workflows/snap-testing.yml
  • actions/checkout v3
  • actions/checkout v3
.github/workflows/unit-testing.yml
  • actions/checkout v3
  • actions/setup-go v4
.github/workflows/versioning.yml
  • Actions-R-Us/actions-tagger v2.0.3
build/action.yml
  • actions/checkout v3
  • snapcore/action-build v1
  • actions/upload-artifact v3
gomod
go.mod
  • go 1.17
  • github.com/stretchr/testify v1.8.3

  • Check this box to trigger a request for Renovate to run again on this repository

Reference port 33333 used by postgres

=== RUN   TestCommon/config/change_service_port/global_config
    log.go:15: [exec] sudo snap set edgex-device-usb-camera app-options=true
    log.go:15: [exec] sudo lsof -nPi :33333 || true
    log.go:15: [stdout] COMMAND   PID        USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
    log.go:15: [stdout] postgres 4756 snap_daemon    8u  IPv6  41544      0t0  UDP [::1]:33333->[::1]:33333 
    log.go:15: [stdout] postgres 4769 snap_daemon    8u  IPv6  41544      0t0  UDP [::1]:33333->[::1]:33333 
    log.go:15: [stdout] postgres 4770 snap_daemon    8u  IPv6  41544      0t0  UDP [::1]:33333->[::1]:33333 
    log.go:15: [stdout] postgres 4771 snap_daemon    8u  IPv6  41544      0t0  UDP [::1]:33333->[::1]:33333 
    log.go:15: [stdout] postgres 4772 snap_daemon    8u  IPv6  41544      0t0  UDP [::1]:33333->[::1]:33333 
    log.go:15: [stdout] postgres 4773 snap_daemon    8u  IPv6  41544      0t0  UDP [::1]:33333->[::1]:33333 
    log.go:15: [stdout] postgres 4774 snap_daemon    8u  IPv6  41544      0t0  UDP [::1]:33333->[::1]:33333 
    log.go:15: [stdout] postgres 4966 snap_daemon    8u  IPv6  41544      0t0  UDP [::1]:33333->[::1]:33333 
    log.go:15: [stdout] postgres 50[85](https://github.com/canonical/edgex-snap-testing/runs/7177704444?check_suite_focus=true#step:4:87) snap_daemon    8u  IPv6  41544      0t0  UDP [::1]:33333->[::1]:33333 
    log.go:15: [stdout] postgres 52[88](https://github.com/canonical/edgex-snap-testing/runs/7177704444?check_suite_focus=true#step:4:90) snap_daemon    8u  IPv6  41544      0t0  UDP [::1]:33333->[::1]:33333 
    log.go:15: [stdout] postgres 52[89](https://github.com/canonical/edgex-snap-testing/runs/7177704444?check_suite_focus=true#step:4:91) snap_daemon    8u  IPv6  41544      0t0  UDP [::1]:33333->[::1]:33333 
    log.go:15: [stdout] postgres 52[90](https://github.com/canonical/edgex-snap-testing/runs/7177704444?check_suite_focus=true#step:4:92) snap_daemon    8u  IPv6  41544      0t0  UDP [::1]:33333->[::1]:33333 
    log.go:15: [stdout] postgres 5301 snap_daemon    8u  IPv6  41544      0t0  UDP [::1]:33333->[::1]:33333 
    net.go:174: Port 33333 is not available

The RequirePortAvailable takes this port as input and fail the tests if the port isn't available. Maybe it is better to have a function such as GetAvailablePort which returns a port that is available.

Allow running all test suites against locally built platform snap

The LOCAL_SNAP environment variable is used for platform or the service snap, depending on the testing suite. The only way to run a device/app/support test against a particular platform snap revision is to use the revision number or branch from the store. This requires first uploading a locally built snap which is very resource consuming.

Example:

PLATFORM_CHANNEL=4259 go test ./test/suites/device-virtual
PLATFORM_CHANNEL=latest/edge/temp-branch go test ./test/suites/device-virtual

The requirement is to allow passing a local snap file path for running any testing suite.

Device USB Camera's RTSP server errors not captured in build logs

The action captures any line containing "error" (case insensitive):

cat $file | grep --ignore-case "error"

The rtsp server uses "ERR" to indicate error logs. Example:

Sep 09 14:32:04 fv-az165-104 edgex-device-usb-camera.rtsp-simple-server[6604]: ERR: yaml: line 75: could not find expected ':'
Sep 09 14:32:04 fv-az165-104 systemd[1]: snap.edgex-device-usb-camera.rtsp-simple-server.service: Main process exited, code=exited, status=1/FAILURE
Sep 09 14:32:04 fv-az165-104 systemd[1]: snap.edgex-device-usb-camera.rtsp-simple-server.service: Failed with result 'exit-code'.
Sep 09 14:32:04 fv-az165-104 systemd[1]: snap.edgex-device-usb-camera.rtsp-simple-server.service: Scheduled restart job, restart counter is at 1.

Refactor the edgex-no-sec tests to allow custom testing

Currently, the setup installs a bunch of snaps even though only the TestRulesEngine tests use them. It is not possible to run other sub-tests without also having Device Virtual, ASC, eKuiper snaps installed and checked. Those should not be in the global setup and teardown, but instead inside the TestRulesEngine test.

Manual edgex-secretstore-token connection should happen after platform installation

Currently, platform snap installation happens after local snap installation and edgex-secret-token connection, and it will cause an error. Here is an example:

[exec] sudo snap install --dangerous app-rfid-llrp-inventory/edgex-app-rfid-llrp-inventory_2.1.0-dev.3_amd64.snap
[stdout] edgex-app-rfid-llrp-inventory 2.1.0-dev.3 installed
[exec] sudo snap connect edgexfoundry:edgex-secretstore-token edgex-app-rfid-llrp-inventory:edgex-secretstore-token
stderr] error: snap "edgexfoundry" is not installed
exit status 1

This need to be fixed in all test suites. Maybe remove following line before local snap installation:

utils.SnapInstallFromStore(nil, "edgexfoundry", utils.PlatformChannel)

Remove config override tests after service startup when using config provider

As expected by #4448, the snap tests have started failing since this change because the overrides no longer apply on top of configurations coming from config provider: Common configuration loaded from the Configuration Provider. No overrides applied

Need to change the snap tests to no longer test that, or make the services run without config provider.

Failing test result:

=== RUN   TestChangeStartupMsg_app
    config_test.go:26: Set and verify new startup message: snap-testing (app)
    exec.go:19: [exec] sudo snap set edgexfoundry apps.support-scheduler.config.service-startupmsg='snap-testing (app)'
    exec.go:19: [exec] sudo snap restart edgexfoundry.support-scheduler
    exec.go:101: [stdout] 2023-03-16T14:24:06Z INFO Waiting for "snap.edgexfoundry.support-scheduler.service" to stop.
    exec.go:101: [stdout] Restarted.
    net.go:132: Retry 1/180: Waiting for ports: 59880 (core-data), 59881 (core-metadata), 59882 (core-command), 8200 (vault), 8500 (consul), [63](https://github.com/edgexfoundry/edgex-go/actions/runs/4431591455/jobs/7788709103?pr=4449#step:2:67)79 (redis)
    config_test.go:109: Retry 1/10: Waiting for startup message: snap-testing (app)
    exec.go:19: [exec] sudo journalctl --since "2023-03-16 14:24:05" --no-pager | grep "edgexfoundry.support-scheduler"|| true
    config_test.go:109: Retry 2/10: Waiting for startup message: snap-testing (app)
    exec.go:19: [exec] sudo journalctl --since "2023-03-16 14:24:05" --no-pager | grep "edgexfoundry.support-scheduler"|| true
    config_test.go:109: Retry 3/10: Waiting for startup message: snap-testing (app)
    exec.go:19: [exec] sudo journalctl --since "2023-03-16 14:24:05" --no-pager | grep "edgexfoundry.support-scheduler"|| true
    config_test.go:109: Retry 4/10: Waiting for startup message: snap-testing (app)
    exec.go:19: [exec] sudo journalctl --since "2023-03-16 14:24:05" --no-pager | grep "edgexfoundry.support-scheduler"|| true
    config_test.go:109: Retry 5/10: Waiting for startup message: snap-testing (app)
    exec.go:19: [exec] sudo journalctl --since "2023-03-16 14:24:05" --no-pager | grep "edgexfoundry.support-scheduler"|| true
    config_test.go:109: Retry 6/10: Waiting for startup message: snap-testing (app)
    exec.go:19: [exec] sudo journalctl --since "2023-03-16 14:24:05" --no-pager | grep "edgexfoundry.support-scheduler"|| true
    config_test.go:109: Retry 7/10: Waiting for startup message: snap-testing (app)
    exec.go:19: [exec] sudo journalctl --since "2023-03-16 14:24:05" --no-pager | grep "edgexfoundry.support-scheduler"|| true
    config_test.go:109: Retry 8/10: Waiting for startup message: snap-testing (app)
    exec.go:19: [exec] sudo journalctl --since "2023-03-16 14:24:05" --no-pager | grep "edgexfoundry.support-scheduler"|| true
    config_test.go:109: Retry 9/10: Waiting for startup message: snap-testing (app)
    exec.go:19: [exec] sudo journalctl --since "2023-03-16 14:24:05" --no-pager | grep "edgexfoundry.support-scheduler"|| true
    config_test.go:109: Retry 10/10: Waiting for startup message: snap-testing (app)
    exec.go:19: [exec] sudo journalctl --since "2023-03-16 14:24:05" --no-pager | grep "edgexfoundry.support-scheduler"|| true
    config_test.go:118: Time out: reached max 10 retries.
    config_test.go:31: 
        	Error Trace:	/home/runner/work/_actions/canonical/edgex-snap-testing/v3/test/suites/edgexfoundry/config_test.go:31
        	Error:      	Should be true
        	Test:       	TestChangeStartupMsg_app
        	Messages:   	new startup message = snap-testing (app)

Service fails to start when core component is not ready

Logs (from here):

  2022-04-02T17:28:02Z edgex-device-mqtt.device-mqtt[5671]: level=INFO ts=2022-04-02T17:28:02.553070694Z app=device-mqtt source=messaging.go:69 msg="Setting options for secure MessageBus with AuthMode='usernamepassword' and SecretName='redisdb"
  2022-04-02T17:28:02Z edgex-device-mqtt.device-mqtt[5671]: level=INFO ts=2022-04-02T17:28:02.565105083Z app=device-mqtt source=messaging.go:97 msg="Connected to redis Message Bus @ redis://localhost:6379 publishing on 'edgex/events/device' prefix topic with AuthMode='usernamepassword'"
  2022-04-02T17:28:02Z edgex-device-mqtt.device-mqtt[5671]: level=INFO ts=2022-04-02T17:28:02.565248388Z app=device-mqtt source=init.go:152 msg="Check core-metadata service's status via Registry..."
  2022-04-02T17:28:02Z edgex-device-mqtt.device-mqtt[5671]: level=ERROR ts=2022-04-02T17:28:02.568653798Z app=device-mqtt source=init.go:161 msg="core-metadata service is not registered. Might not have started... "
  2022-04-02T17:28:03Z systemd[1]: Stopping Service for snap application edgex-device-mqtt.device-mqtt...

We may need to extend the startup duration and interval: Common Environment Variables - EdgeX Foundry Documentation

Alternatively, we change the tests to wait for the required services before kicking off the tests.

Test action produces too much logs on error

The snap logs are grouped. The group is collapsed when there is no error. However, the group is open by default when some testing errors happen. Because the logs are too long, this makes it very difficult to see the actual testing results.

Options:

  1. Print only errors and upload full log as artifact
  2. Find a way to keep the logs always collapsed

Refactor snap and channel variable names

PR #219 added a new variable called LOCAL_PLATFORM_SNAP, resulting in conditional value passing in the action (see below) and an incorrect reference to the Config Provider snap as a "service" (see below).

LOCAL_PLATFORM_SNAP: ${{ inputs.name == 'edgexfoundry' && steps.path.outputs.local_snap || '' }}
LOCAL_SERVICE_SNAP: ${{ inputs.name != 'edgexfoundry' && steps.path.outputs.local_snap || '' }}

Instead of

  • PLATFORM_CHANNEL
  • SERVICE_CHANNEL
  • LOCAL_PLATFORM_SNAP
  • LOCAL_SERVICE_SNAP

We could use the following variables:

  • PLATFORM_SNAP AND SNAP for path to snap file - matching the action input: snap.
  • PLATFORM_CHANNEL AND CHANNEL for snap channel when downloading from store - matching the action input: channel. The platform_channel used only in config provider testing workflow can be removed.

For platform/no-sec tests:

  • If PLATFORM_SNAP is not set, use SNAP as its value
  • If PLATFORM_CHANNEL is not set, use CHANNEL as its value

To run a service test or config provider test and override both snaps platform and the testes snap, set PLATFORM_SNAP=locally-build-edgex-go.snap SNAP=locally-built-app.snap

To test the platform with a local snap: SNAP=locally-build-edgex-go.snap which would be equivalent to PLATFORM_SNAP=locally-build-edgex-go.snap because of the above conditional initializations.

Related issue: #60

Exec util randomly fails to return standard output

=== RUN   TestExec/one_command
    exec.go:82: [exec] echo "hi"
    exec_test.go:16: 
        	Error Trace:	exec_test.go:16
        	Error:      	Not equal: 
        	            	expected: "hi\n"
        	            	actual  : ""
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -1,2 +1 @@
        	            	-hi
        	            	 
        	Test:       	TestExec/one_command

E.g.:

Reproduce:
Comment out the slow "exit after slow command" subtest and run:

go test ./test/utils -count=1000

App service configurable profile config test prints all service logs

The test searches for the profile using Go string matching functions but as a side effect all the logs are printed. This is because tests run with verbosity and the output of the snap logs command is printed out.

logs := utils.SnapLogsJournal(t, start, ascSnap)
expectLog := "app=app-" + profile
require.True(t, strings.Contains(logs, expectLog))

We should implement a new function which fetches logs and uses grep to search for the expected string.

Bind address already in use

Tests occasionally fail on Github because the bind address with default ports isn’t available. It isn’t clear if there is an error in services or if the ports are used by some other processes. We’ve seen this for 59880 (core data), 59882 (core command).

When this happens, the services restart very quickly and then disable. This is because the default restart delay from systemd is 100ms without any increments, unlike docker. See systemd/systemd#6129

The attached logs shows core command restarting 9 times in 7 seconds before being permanently stopped:

Aug 12 14:02:29 fv-az41-871 edgexfoundry.core-command[5432]: level=ERROR ts=2022-08-12T14:02:29.075056424Z app=core-command source=httpserver.go:146 msg="Web server failed: listen tcp 127.0.0.1:59882: bind: address already in use"
...
Aug 12 14:02:30 fv-az41-871 systemd[1]: snap.edgexfoundry.core-command.service: Main process exited, code=exited, status=1/FAILURE
Aug 12 14:02:30 fv-az41-871 systemd[1]: snap.edgexfoundry.core-command.service: Failed with result 'exit-code'.
Aug 12 14:02:30 fv-az41-871 systemd[1]: snap.edgexfoundry.core-command.service: Scheduled restart job, restart counter is at 9.
Aug 12 14:02:30 fv-az41-871 systemd[1]: Stopped Service for snap application edgexfoundry.core-command.
Aug 12 14:02:30 fv-az41-871 systemd[1]: snap.edgexfoundry.core-command.service: Start request repeated too quickly.
Aug 12 14:02:30 fv-az41-871 systemd[1]: snap.edgexfoundry.core-command.service: Failed with result 'exit-code'.
Aug 12 14:02:30 fv-az41-871 systemd[1]: Failed to start Service for snap application edgexfoundry.core-command.

Example test result and logs:

Related issue in tests: #79, where test port 33333 is not available.

Make Teardown an optional step

When running the tests on ephemeral CI instances, the removal of snaps in the teardown is unnecessary and time consuming.

It would be beneficial to allow skipping of the cleanup operations after the tests and set that to default in Github workflows and other automated tests.

Refactor suites to use predefined ports from utils package

The port numbers are set in different suites but also in the utils package.

It would be cleaner to simply use the ports set in the utils package.
For example, the port for Device Virtual can be retrieved with utils.ServicePort("device-virtual").

We may want to also add constants for the service names to avoid human errors.

Inconsistent variable names for channel input

The channel of the snap that is tested is passed to the action using the channel input:

channel:
description: |
Channel for downloading the snap from store.
This is useful only when 'snap' input is not set.
required: false

However, the environment variable and internal global variables are "service channel":

SERVICE_CHANNEL: ${{inputs.channel}}

ServiceChannel = "latest/edge"

This adds some confusion because when testing edgexfoundry, the channel input makes sense from the workflow but internally, the "service channel" is misleading since this isn't a service.

I suggest renaming the env var and global var to just "channel" to be consistent with the action input.

A warning of invalid test repo should be marked as an error

Example: Warning: Unexpected input(s) 'name', 'snap', valid inputs are ['repo']

Here is the full logs.

This type of warning should be treated as a failure, instead of passing the test. The above log shows that the workflow only built the test, without testing it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.