Add validation of db before and after modification

Add various consistency checks of the database:
- no duplicate extension SDKs
- no duplicate module requirements in one SDK
- no version requirements "moving backwards"

..with associated tests.

Also add verification of the checked in database to presubmit tests
(the build of the binarypb should also fail).

Bug: 173188089
Test: atest gen_sdk_test
Change-Id: I10436cdde1e8829065d77be53d5f10b7264a5a58
diff --git a/gen_sdk/Android.bp b/gen_sdk/Android.bp
index b9ac715..c5890fb 100644
--- a/gen_sdk/Android.bp
+++ b/gen_sdk/Android.bp
@@ -28,7 +28,10 @@
 sh_test_host {
     name: "gen_sdk_test",
     src: "gen_sdk_test.sh",
-    data: ["testdata/**/*"],
+    data: [
+        "extensions_db.textpb",
+        "testdata/**/*"
+    ],
     required: ["gen_sdk"],
     test_suites: ["general-tests"],
 }
diff --git a/gen_sdk/gen_sdk.py b/gen_sdk/gen_sdk.py
index c85243c..3353fd4 100644
--- a/gen_sdk/gen_sdk.py
+++ b/gen_sdk/gen_sdk.py
@@ -19,6 +19,9 @@
 # Print a binary representation of the proto database.
 $ gen_sdk --action print_binary
 
+# Validate the database
+$ gen_sdk --action validate
+
 # Create a new SDK
 $ gen_sdk --action new_sdk --sdk 1 --modules=IPSEC,SDK_EXTENSIONS
 """
@@ -45,10 +48,10 @@
   )
   parser.add_argument(
     '--action',
-    choices=['print_binary', 'new_sdk'],
+    choices=['print_binary', 'new_sdk', 'validate'],
     metavar='ACTION',
     required=True,
-    help='Which action to take (print_binary|new_sdk).'
+    help='Which action to take (print_binary|new_sdk|validate).'
   )
   parser.add_argument(
     '--sdk',
@@ -69,6 +72,41 @@
   sys.stdout.buffer.write(database.SerializeToString())
 
 
+def ValidateDatabase(database, dbname):
+  def find_duplicate(l):
+    s = set()
+    for i in l:
+      if i in s:
+        return i
+      s.add(i)
+    return None
+
+  def find_bug():
+    dupe = find_duplicate([v.version for v in database.versions])
+    if dupe:
+      return 'Found duplicate extension version: %d' % dupe
+
+    for version in database.versions:
+      dupe = find_duplicate([r.module for r in version.requirements])
+      if dupe:
+        return 'Found duplicate module requirement for %s in single version %s' % (dupe, version)
+
+    prev_requirements = {}
+    for version in sorted(database.versions, key=lambda v: v.version):
+      for requirement in version.requirements:
+        if requirement.module in prev_requirements:
+          prev = prev_requirements[requirement.module]
+          if prev.version > requirement.version.version:
+            return 'Found module requirement moving backwards: %s in %s' % (requirement, version)
+        prev_requirements[requirement.module] = requirement.version
+    return None
+
+  err = find_bug()
+  if err is not None:
+    print('%s not valid, aborting:\n  %s' % (dbname, err))
+    sys.exit(1)
+
+
 def NewSdk(database, new_version, modules):
   new_requirements = {}
 
@@ -98,12 +136,16 @@
   if args.modules:
     modules = [SdkModule.Value(m) for m in args.modules.split(',')]
 
+  ValidateDatabase(database, 'Input database')
+
   {
+    'validate': lambda : print('Validated database'),
     'print_binary': lambda : PrintBinary(database),
     'new_sdk': lambda : NewSdk(database, args.sdk, modules)
   }[args.action]()
 
   if args.action in ['new_sdk']:
+    ValidateDatabase(database, 'Post-modification database')
     with args.database.open('w') as f:
       f.write(google.protobuf.text_format.MessageToString(database))
 
diff --git a/gen_sdk/gen_sdk_test.sh b/gen_sdk/gen_sdk_test.sh
index 80e21ca..bdf35b7 100644
--- a/gen_sdk/gen_sdk_test.sh
+++ b/gen_sdk/gen_sdk_test.sh
@@ -42,3 +42,41 @@
   diff -u0 testdata/test_extensions_db.textpb extensions_db.textpb
 }
 test_new_sdk
+
+# Verifies the tool won't allow bogus SDK updates
+function test_validate() {
+  set +e
+
+  rm -f extensions_db.textpb && echo bogus > extensions_db.textpb
+  if gen_sdk --action validate; then
+    echo "expected validate to fail on bogus db"
+    exit 1
+  fi
+
+  rm -f extensions_db.textpb && touch extensions_db.textpb
+  gen_sdk --action new_sdk --sdk 1 --modules MEDIA_PROVIDER
+  if gen_sdk --action new_sdk --sdk 1 --modules SDK_EXTENSIONS; then
+    echo "FAILED: expected duplicate sdk numbers to fail"
+    echo "DB:"
+    cat extensions_db.textpb
+    exit 1
+  fi
+
+  if gen_sdk --action validate --database testdata/dupe_req.textpb; then
+    echo "FAILED: expected duplicate module in one sdk level to fail"
+    exit 1
+  fi
+
+  if gen_sdk --action validate --database testdata/backward_req.textpb; then
+    echo "FAILED: expect version requirement going backward to fail"
+    exit 1
+  fi
+
+  set -e
+}
+test_validate
+
+function test_checked_in_db() {
+  gen_sdk --action validate --database extensions_db.textpb
+}
+test_checked_in_db
diff --git a/gen_sdk/testdata/backward_req.textpb b/gen_sdk/testdata/backward_req.textpb
new file mode 100644
index 0000000..f77604a
--- /dev/null
+++ b/gen_sdk/testdata/backward_req.textpb
@@ -0,0 +1,30 @@
+versions {
+  version: 1
+  requirements {
+    module: MEDIA_PROVIDER
+    version {
+      version: 2
+    }
+  }
+}
+versions {
+  version: 2
+  requirements {
+    module: MEDIA_PROVIDER
+    version {
+      version: 1
+    }
+  }
+  requirements {
+    module: MEDIA
+    version {
+      version: 2
+    }
+  }
+  requirements {
+    module: IPSEC
+    version {
+      version: 2
+    }
+  }
+}
diff --git a/gen_sdk/testdata/dupe_req.textpb b/gen_sdk/testdata/dupe_req.textpb
new file mode 100644
index 0000000..1280379
--- /dev/null
+++ b/gen_sdk/testdata/dupe_req.textpb
@@ -0,0 +1,15 @@
+versions {
+  version: 1
+  requirements {
+    module: MEDIA_PROVIDER
+    version {
+      version: 1
+    }
+  }
+  requirements {
+    module: MEDIA_PROVIDER
+    version {
+      version: 2
+    }
+  }
+}