cvetriager: context generator: check stable kernels for patches

New functions were added to search for patches in the stable kernels.
Now additional common file was added to take care of common functions
between contextgenerator and patchapplier.

BUG=chromium:1093363
TEST=python setup.py test

Change-Id: Ia3e14dbc3f4013cbbab1c239868627e116925b0d
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/2252062
Reviewed-by: Zubin Mithra <zsm@chromium.org>
Commit-Queue: Wanda Mora <morawand@chromium.org>
Tested-by: Wanda Mora <morawand@chromium.org>
diff --git a/contrib/cvetriager/cvelib/common.py b/contrib/cvetriager/cvelib/common.py
new file mode 100644
index 0000000..59332dd
--- /dev/null
+++ b/contrib/cvetriager/cvelib/common.py
@@ -0,0 +1,46 @@
+# -*-coding: utf-8 -*-
+
+"""Module containing shared helper methods."""
+
+import subprocess
+
+
+class CommonException(Exception):
+    """Exception raised from common."""
+
+
+def get_stable_branch(kernel):
+    """Returns stable branch name."""
+    branch = kernel[1:]
+    return f'linux-{branch}.y'
+
+
+def get_cros_branch(kernel):
+    """Returns chromeos branch name."""
+    branch = kernel[1:]
+    return f'chromeos-{branch}'
+
+
+def checkout_branch(kernel, branch, remote, remote_branch, kernel_path):
+    """Checks into appropriate branch and keeps it up to date."""
+    do_checkout(kernel, branch, kernel_path)
+    do_pull(kernel, remote, remote_branch, kernel_path)
+
+
+def do_checkout(kernel, branch, kernel_path):
+    """Checks into given branch."""
+    try:
+        subprocess.check_call(['git', 'checkout', branch], stdout=subprocess.DEVNULL,
+                              stderr=subprocess.DEVNULL, cwd=kernel_path)
+    except subprocess.CalledProcessError:
+        raise CommonException('Checkout failed for %s' % kernel)
+
+
+def do_pull(kernel, remote, remote_branch, kernel_path):
+    """Pulls from given branch."""
+    try:
+        subprocess.check_call(['git', 'pull', remote, remote_branch],
+                              stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
+                              cwd=kernel_path)
+    except subprocess.CalledProcessError:
+        raise CommonException('Pull failed for %s' % kernel)
diff --git a/contrib/cvetriager/cvelib/contextgenerator.py b/contrib/cvetriager/cvelib/contextgenerator.py
index 40992fc..2714c1a 100644
--- a/contrib/cvetriager/cvelib/contextgenerator.py
+++ b/contrib/cvetriager/cvelib/contextgenerator.py
@@ -1,26 +1,32 @@
 # -*-coding: utf-8 -*-
 
-"""Context generator for CVE Triage tool
+"""Context generator for CVE Triage tool.
 """
 
 import subprocess
 import os
 import re
 
+from cvelib import common
 from cvelib import patchapplier
 
+
+class ContextGeneratorException(Exception):
+    """Exception raised from ContextGenerator."""
+
+
 class ContextGenerator():
-    """Determines which kernels a commit should be applied to"""
+    """Determines which kernels a commit should be applied to."""
 
     def __init__(self, kernels):
         self.kernels = kernels
         self.relevant_commits = []
 
     def get_fixes_commit(self, linux_sha):
-        """Returns Fixes: tag's commit sha"""
+        """Returns Fixes: tag's commit sha."""
         commit_message = patchapplier.get_commit_message(os.getenv('LINUX'), linux_sha)
 
-        # Collects 'Fixes: {sha}' string from commit_message
+        # Collects 'Fixes: {sha}' string from commit_message.
         m = re.findall('^Fixes: [a-z0-9]{12}', commit_message, re.M)
 
         if not m:
@@ -32,7 +38,7 @@
         return sha
 
     def get_subject_line(self, linux_sha):
-        """Returns subject line of given sha"""
+        """Returns subject line of given sha."""
         try:
             subject = subprocess.check_output(['git', 'log', '--pretty=format:%s', '-n', '1',
                                               linux_sha], stderr=subprocess.DEVNULL,
@@ -42,8 +48,37 @@
 
         return subject
 
+    def is_in_kernel(self, path, subject, is_inside):
+        """Searches through kernel for a given subject."""
+        try:
+            result = subprocess.check_output(['git', 'log', '--no-merges', '-F', '--grep',
+                                             subject], stderr=subprocess.DEVNULL,
+                                             cwd=path, encoding='utf-8')
+            if bool(result) == is_inside:
+                return True
+        except subprocess.CalledProcessError:
+            pass
+
+        return False
+
+    def checkout_branch(self, branch_name, kernel):
+        """Checking into appropriate branch."""
+        try:
+            branch = branch_name % kernel[1:]
+            subprocess.check_call(['git', 'checkout', branch], stdout=subprocess.DEVNULL,
+                                  stderr=subprocess.DEVNULL, cwd=os.getenv('STABLE'))
+        except subprocess.CalledProcessError:
+            raise ContextGeneratorException('Checkout failed for %s' % branch)
+
+        try:
+            subprocess.check_call(['git', 'pull', branch],
+                                  stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
+                                  cwd=os.getenv('STABLE'))
+        except subprocess.CalledProcessError:
+            raise ContextGeneratorException('Remote branch %s does not exist' % branch)
+
     def filter_fixed_kernels(self, sha):
-        """Filters out kernels that are already fixed"""
+        """Filters out kernels that are already fixed."""
         valid_kernels = []
 
         subject = self.get_subject_line(sha)
@@ -51,21 +86,32 @@
         for kernel in self.kernels:
             kernel_path = os.path.join(os.getenv('CHROMIUMOS_KERNEL'), kernel)
 
-            patchapplier.checkout_branch(kernel, kernel_path)
+            branch = common.get_cros_branch(kernel)
+            common.checkout_branch(kernel, f'cros/{branch}', 'cros', branch, kernel_path)
 
-            try:
-                result = subprocess.check_output(['git', 'log', '--no-merges', '-F', '--grep',
-                                                 subject], stderr=subprocess.DEVNULL,
-                                                 cwd=kernel_path, encoding='utf-8')
-                if not bool(result):
-                    valid_kernels.append(kernel)
-            except subprocess.CalledProcessError:
-                pass
+            if self.is_in_kernel(kernel_path, subject, False):
+                valid_kernels.append(kernel)
+
+        self.kernels = valid_kernels
+
+    def filter_based_on_stable(self, linux_sha, environment):
+        """Filters out kernels with linux commit already in linux-stable."""
+        valid_kernels = []
+
+        subject = self.get_subject_line(linux_sha)
+
+        for kernel in self.kernels:
+            branch = common.get_stable_branch(kernel)
+
+            common.checkout_branch(kernel, branch, 'origin', branch, environment)
+
+            if self.is_in_kernel(environment, subject, False):
+                valid_kernels.append(kernel)
 
         self.kernels = valid_kernels
 
     def find_kernels_with_fixes_subj(self, linux_sha):
-        """Filters out kernels without fixes commit given through the linux sha"""
+        """Filters out kernels without fixes commit given through the linux sha."""
         valid_kernels = []
 
         fixes_sha = self.get_fixes_commit(linux_sha)
@@ -80,22 +126,16 @@
 
             kernel_path = os.path.join(os.getenv('CHROMIUMOS_KERNEL'), kernel)
 
-            patchapplier.checkout_branch(kernel, kernel_path)
+            branch = common.get_cros_branch(kernel)
+            common.checkout_branch(kernel, f'cros/{branch}', 'cros', branch, kernel_path)
 
-            try:
-                result = subprocess.check_output(['git', 'log', '--no-merges', '-F', '--grep',
-                                                 subject], stderr=subprocess.DEVNULL,
-                                                 cwd=kernel_path, encoding='utf-8')
-                if bool(result):
-                    valid_kernels.append(kernel)
-            except subprocess.CalledProcessError:
-                pass
+            if self.is_in_kernel(kernel_path, subject, True):
+                valid_kernels.append(kernel)
 
         self.kernels = valid_kernels
 
     def detect_relevant_commits(self, linux_sha):
-        """Displays information about fixes that refer to the linux sha"""
-
+        """Displays information about fixes that refer to the linux sha."""
         linux_subj = self.get_subject_line(linux_sha)
 
         shas = subprocess.check_output(['git', 'log', '--format=%H'],
@@ -116,8 +156,8 @@
             try:
                 fixes_subj = self.get_subject_line(fixes_sha)
             except ContextGeneratorException:
-                # Given sha contains fixes tag from another working tree
-                # Ex: 1bb0fa189c6a is refered to by a7868323c2638a7c6c5b30b37831b73cbdf0dc15
+                # Given sha contains fixes tag from another working tree.
+                # Ex: 1bb0fa189c6a is refered to by a7868323c2638a7c6c5b30b37831b73cbdf0dc15.
                 pass
 
             if fixes_subj == linux_subj:
@@ -126,12 +166,13 @@
                 return
 
     def generate_context(self, linux_sha):
-        """Generates list of kernels with same commit as provided by linux_sha"""
+        """Generates list of kernels with same commit as provided by linux_sha."""
         self.filter_fixed_kernels(linux_sha)
 
         self.find_kernels_with_fixes_subj(linux_sha)
 
-        self.detect_relevant_commits(linux_sha)
+        self.filter_based_on_stable(linux_sha, os.getenv('STABLE'))
 
-class ContextGeneratorException(Exception):
-    """Exception raised from ContextGenerator"""
+        self.filter_based_on_stable(linux_sha, os.getenv('STABLE_RC'))
+
+        self.detect_relevant_commits(linux_sha)
diff --git a/contrib/cvetriager/cvelib/patchapplier.py b/contrib/cvetriager/cvelib/patchapplier.py
index ff69560..8b7264a 100644
--- a/contrib/cvetriager/cvelib/patchapplier.py
+++ b/contrib/cvetriager/cvelib/patchapplier.py
@@ -7,9 +7,15 @@
 import sys
 import os
 
-def get_sha(kernel_path):
-    """Returns most recent commit sha"""
+from cvelib import common
 
+
+class PatchApplierException(Exception):
+    """Exception raised from patchapplier."""
+
+
+def get_sha(kernel_path):
+    """Returns most recent commit sha."""
     try:
         sha = subprocess.check_output(['git', 'log', '-1', '--format=%H'],
                                       stderr=subprocess.DEVNULL, cwd=kernel_path,
@@ -19,9 +25,9 @@
 
     return sha.rstrip('\n')
 
-def get_commit_message(kernel_path, sha):
-    """Returns commit message"""
 
+def get_commit_message(kernel_path, sha):
+    """Returns commit message."""
     try:
         cmd = ['git', '-C', kernel_path, 'log', '--format=%B', '-n', '1', sha]
         commit_message = subprocess.check_output(cmd, encoding='utf-8')
@@ -31,9 +37,9 @@
         raise PatchApplierException('Could not retrieve commit in kernal path %s for sha %s'
                                     % (kernel_path, sha))
 
-def create_commit_message(kernel_path, sha, bug_id):
-    """Generates new commit message"""
 
+def create_commit_message(kernel_path, sha, bug_id):
+    """Generates new commit message."""
     bug_test_line = f'BUG=chromium:{bug_id}\nTEST=CQ\n\n'
 
     org_msg = get_commit_message(kernel_path, sha)
@@ -42,9 +48,9 @@
 
     return f'UPSTREAM: {org_msg}{cherry_picked}{bug_test_line}'
 
-def fetch_linux_kernel(kernel_path):
-    """Fetch LINUX repo in CHROMIUMOS_KERNEL"""
 
+def fetch_linux_kernel(kernel_path):
+    """Fetch LINUX repo in CHROMIUMOS_KERNEL."""
     if os.getenv('LINUX') == '':
         raise PatchApplierException('Environment variable LINUX is not set')
 
@@ -58,22 +64,9 @@
     except FileNotFoundError:
         raise PatchApplierException('Kernel is non-existent')
 
-def checkout_branch(kernel, kernel_path):
-    """Checking into appropriate branch"""
-
-    try:
-        branch = 'cros/chromeos-' + kernel[1:]
-        subprocess.check_call(['git', 'checkout', branch], stdout=subprocess.DEVNULL,
-                              stderr=subprocess.DEVNULL, cwd=kernel_path)
-        subprocess.check_call(['git', 'pull', 'cros', 'chromeos-' + kernel[1:]],
-                              stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
-                              cwd=kernel_path)
-    except subprocess.CalledProcessError:
-        raise PatchApplierException('Checkout failed for %s' % kernel)
 
 def cherry_pick(kernel_path, sha, bug_id):
-    """Cherry-picking commit into kernel"""
-
+    """Cherry-picking commit into kernel."""
     fix_commit_message = create_commit_message(kernel_path, sha, bug_id)
 
     try:
@@ -90,9 +83,9 @@
                           cwd=kernel_path)
     return True
 
-def apply_patch(sha, bug_id, kernel_versions):
-    """Applies patch from LINUX to Chromium OS kernel"""
 
+def apply_patch(sha, bug_id, kernel_versions):
+    """Applies patch from LINUX to Chromium OS kernel."""
     cp_status = {}
 
     if os.getenv('CHROMIUMOS_KERNEL') == '':
@@ -104,11 +97,9 @@
 
         fetch_linux_kernel(kernel_path)
 
-        checkout_branch(kernel, kernel_path)
+        branch = common.get_cros_branch(kernel)
+        common.checkout_branch(kernel, f'cros/{branch}', 'cros', branch, kernel_path)
 
         cp_status[kernel] = cherry_pick(kernel_path, sha, bug_id)
 
     return cp_status
-
-class PatchApplierException(Exception):
-    """Exception raised from PatchApplier."""
diff --git a/contrib/cvetriager/tests/common_test.py b/contrib/cvetriager/tests/common_test.py
new file mode 100644
index 0000000..efb66ce
--- /dev/null
+++ b/contrib/cvetriager/tests/common_test.py
@@ -0,0 +1,44 @@
+# -*-coding: utf-8 -*-
+
+"""Testing script for cvelib/common.py."""
+
+import unittest
+from unittest import mock
+import tempfile
+import subprocess
+
+from cvelib import common
+
+
+class TestCommon(unittest.TestCase):
+    """Test class for cvelib/common.py."""
+
+    def setUp(self):
+        self.temp = tempfile.mkdtemp()
+        subprocess.check_call(['git', 'init'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
+                              cwd=self.temp)
+
+        subprocess.check_call(['git', 'checkout', '-b', 'branch-1.0'], stdout=subprocess.DEVNULL,
+                              stderr=subprocess.DEVNULL, cwd=self.temp)
+        subprocess.check_call(['touch', 'file1'], stdout=subprocess.DEVNULL,
+                              stderr=subprocess.DEVNULL, cwd=self.temp)
+        subprocess.check_call(['git', 'add', 'file1'], stdout=subprocess.DEVNULL,
+                              stderr=subprocess.DEVNULL, cwd=self.temp)
+        subprocess.check_call(['git', 'commit', '-m', 'random commit'], stdout=subprocess.DEVNULL,
+                              stderr=subprocess.DEVNULL, cwd=self.temp)
+
+        subprocess.check_call(['git', 'checkout', '-b', 'branch-2.0'], stdout=subprocess.DEVNULL,
+                              stderr=subprocess.DEVNULL, cwd=self.temp)
+
+    @mock.patch('cvelib.common.do_pull')
+    def test_checkout_branch(self, _):
+        """Unit test for checkout_branch."""
+        kernel = 'v1.0'
+
+        common.checkout_branch(kernel, 'branch-1.0', 'origin', 'branch-1.0', self.temp)
+
+        # Outputs the current branch.
+        branch = subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
+                                         stderr=subprocess.DEVNULL, cwd=self.temp)
+
+        self.assertEqual(branch.rstrip(), b'branch-1.0')
diff --git a/contrib/cvetriager/tests/contextgenerator_test.py b/contrib/cvetriager/tests/contextgenerator_test.py
index feb4007..caaaecf 100644
--- a/contrib/cvetriager/tests/contextgenerator_test.py
+++ b/contrib/cvetriager/tests/contextgenerator_test.py
@@ -1,6 +1,6 @@
 # -*-coding: utf-8 -*-
 
-"""Testing script for cvelib/contextgenerator.py"""
+"""Testing script for cvelib/contextgenerator.py."""
 
 import unittest
 from unittest import mock
@@ -10,14 +10,39 @@
 
 from cvelib import contextgenerator
 
+
+def create_branch_and_commit_file(path, branch_name, file_name, subject):
+    """Creates new branch and commits a new file to it."""
+    subprocess.check_call(['git', 'checkout', '-b', branch_name],
+                          stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
+                          cwd=path)
+    subprocess.check_call(['touch', file_name], stdout=subprocess.DEVNULL,
+                          stderr=subprocess.DEVNULL, cwd=path)
+    subprocess.check_call(['git', 'add', file_name], stdout=subprocess.DEVNULL,
+                          stderr=subprocess.DEVNULL, cwd=path)
+    subprocess.check_call(['git', 'commit', '-m', subject], stdout=subprocess.DEVNULL,
+                          stderr=subprocess.DEVNULL, cwd=path)
+
+
 class TestContextGenerator(unittest.TestCase):
-    """Test class for cvelib/contextgenerator.py"""
+    """Test class for cvelib/contextgenerator.py."""
+
+    # Refers to commit in the linux kernel.
+    linux_sha = '54e200ab40fc14c863bcc80a51e20b7906608fce'
+
+    # Subject of linux_sha.
+    linux_subject = 'kernel/relay.c: handle alloc_percpu returning NULL in relay_open'
+
+    # Subject of commit given by fixes tag in linux_sha.
+    fixes_subject = 'relay: Use per CPU constructs for the relay channel buffer pointers'
 
     def setUp(self):
-        # Backup $CHROMIUMOS_KERNEL
+        # Backup $CHROMIUMOS_KERNEL, $STABLE, $STABLE_RC.
         self.cros_kernel = os.getenv('CHROMIUMOS_KERNEL')
+        self.stable = os.getenv('STABLE')
+        self.stable_rc = os.getenv('STABLE_RC')
 
-        # Make temporary directory for $CHROMIUMOS_KERNEL
+        # Make temporary directory for $CHROMIUMOS_KERNEL.
         self.cros_temp = tempfile.mkdtemp()
         os.environ['CHROMIUMOS_KERNEL'] = self.cros_temp
         self.kernel_temp1 = os.path.join(self.cros_temp, 'v1.0')
@@ -29,25 +54,33 @@
         subprocess.check_call(['git', 'init'], stdout=subprocess.DEVNULL,
                               stderr=subprocess.DEVNULL, cwd=self.kernel_temp2)
 
-        fixes_subject = 'relay: Use per CPU constructs for the relay channel buffer pointers'
+        create_branch_and_commit_file(self.kernel_temp1, 'cros/chromeos-1.0', 'new_file',
+                                      TestContextGenerator.fixes_subject)
 
-        # Adds commit to v1.0 with expected commit subject
-        subprocess.check_call(['touch', 'new_file'], stdout=subprocess.DEVNULL,
-                              stderr=subprocess.DEVNULL, cwd=self.kernel_temp1)
-        subprocess.check_call(['git', 'add', 'new_file'], stdout=subprocess.DEVNULL,
-                              stderr=subprocess.DEVNULL, cwd=self.kernel_temp1)
-        subprocess.check_call(['git', 'commit', '-m', fixes_subject], stdout=subprocess.DEVNULL,
-                              stderr=subprocess.DEVNULL, cwd=self.kernel_temp1)
+        create_branch_and_commit_file(self.kernel_temp2, 'cros/chromeos-2.0', 'new_file2',
+                                      TestContextGenerator.linux_subject)
 
-        linux_subject = 'kernel/relay.c: handle alloc_percpu returning NULL in relay_open'
+        # Helps test if commit is in $STABLE.
+        self.stable_temp = tempfile.mkdtemp()
+        os.environ['STABLE'] = self.stable_temp
+        subprocess.check_call(['git', 'init'], stdout=subprocess.DEVNULL,
+                              stderr=subprocess.DEVNULL, cwd=self.stable_temp)
 
-        # Helps test the filtering out of an already fixed kernel
-        subprocess.check_call(['touch', 'new_file2'], stdout=subprocess.DEVNULL,
-                              stderr=subprocess.DEVNULL, cwd=self.kernel_temp2)
-        subprocess.check_call(['git', 'add', 'new_file2'], stdout=subprocess.DEVNULL,
-                              stderr=subprocess.DEVNULL, cwd=self.kernel_temp2)
-        subprocess.check_call(['git', 'commit', '-m', linux_subject], stdout=subprocess.DEVNULL,
-                              stderr=subprocess.DEVNULL, cwd=self.kernel_temp2)
+        create_branch_and_commit_file(self.stable_temp, 'linux-1.0.y', 'file1', 'random subject')
+
+        create_branch_and_commit_file(self.stable_temp, 'linux-2.0.y', 'file2',
+                                      TestContextGenerator.linux_subject)
+
+        # Helps test if commit is in $STABLE_RC.
+        self.stable_rc_temp = tempfile.mkdtemp()
+        os.environ['STABLE_RC'] = self.stable_rc_temp
+        subprocess.check_call(['git', 'init'], stdout=subprocess.DEVNULL,
+                              stderr=subprocess.DEVNULL, cwd=self.stable_rc_temp)
+
+        create_branch_and_commit_file(self.stable_rc_temp, 'linux-1.0.y', 'file', 'random subject')
+
+        create_branch_and_commit_file(self.stable_rc_temp, 'linux-2.0.y', 'file2',
+                                      TestContextGenerator.linux_subject)
 
     def tearDown(self):
         if self.cros_kernel:
@@ -55,74 +88,106 @@
         else:
             del os.environ['CHROMIUMOS_KERNEL']
 
-        subprocess.check_call(['rm', '-rf', self.cros_temp])
+        if self.stable:
+            os.environ['STABLE'] = self.stable
+        else:
+            del os.environ['STABLE']
 
-    @mock.patch('cvelib.patchapplier.checkout_branch')
+        if self.stable_rc:
+            os.environ['STABLE_RC'] = self.stable_rc
+        else:
+            del os.environ['STABLE_RC']
+
+        subprocess.check_call(['rm', '-rf', self.cros_temp])
+        subprocess.check_call(['rm', '-rf', self.stable_temp])
+        subprocess.check_call(['rm', '-rf', self.stable_rc_temp])
+
+    @mock.patch('cvelib.common.do_pull')
     def test_generate_context(self, _):
-        """Unit test for generate_context"""
+        """Unit test for generate_context."""
         cg = contextgenerator.ContextGenerator(['v1.0', 'v2.0'])
 
-        linux_sha = '54e200ab40fc14c863bcc80a51e20b7906608fce'
-
-        cg.generate_context(linux_sha)
+        cg.generate_context(TestContextGenerator.linux_sha)
 
         self.assertIn('v1.0', cg.kernels)
         self.assertNotIn('v2.0', cg.kernels)
 
     def test_get_subject_line(self):
-        """Unit test for get_subject_line"""
+        """Tests that correct subject of given commit is returned."""
         cg = contextgenerator.ContextGenerator([])
 
-        sha = '54e200ab40fc14c863bcc80a51e20b7906608fce'
+        subject = cg.get_subject_line(TestContextGenerator.linux_sha)
 
-        subject = cg.get_subject_line(sha)
-
-        expected_subject = 'kernel/relay.c: handle alloc_percpu returning NULL in relay_open'
-
-        self.assertEqual(subject, expected_subject)
+        self.assertEqual(subject, TestContextGenerator.linux_subject)
 
     def test_get_fixes_commit(self):
-        """Unit test for get_fixes_commit"""
+        """Tests if correct sha from Fixes: tag is returned."""
         cg = contextgenerator.ContextGenerator([])
 
-        sha = '54e200ab40fc14c863bcc80a51e20b7906608fce'
+        fixes_sha = cg.get_fixes_commit(TestContextGenerator.linux_sha)
 
-        fixes_sha = cg.get_fixes_commit(sha)
-
+        # Commit that is refered to by linux_sha in fixes tag.
         expected_sha = '017c59c042d0'
 
         self.assertEqual(fixes_sha, expected_sha)
 
-    @mock.patch('cvelib.patchapplier.checkout_branch')
+    @mock.patch('cvelib.common.do_pull')
     def test_filter_fixed_kernels(self, _):
-        """Unit test for filter_fixed_kernels"""
+        """Tests that kernels with same commit as linux_sha are filtered out."""
         cg = contextgenerator.ContextGenerator(['v1.0', 'v2.0'])
 
-        sha = '54e200ab40fc14c863bcc80a51e20b7906608fce'
-
-        cg.filter_fixed_kernels(sha)
+        cg.filter_fixed_kernels(TestContextGenerator.linux_sha)
 
         self.assertIn('v1.0', cg.kernels)
         self.assertNotIn('v2.0', cg.kernels)
 
-    @mock.patch('cvelib.patchapplier.checkout_branch')
+    @mock.patch('cvelib.common.do_pull')
     def test_find_kernels_with_fixes_subj(self, _):
-        """Unit test for find_kernels_with_fixes_subj"""
+        """Tests that kernels without specific fix commit are filtered out."""
         cg = contextgenerator.ContextGenerator(['v1.0', 'v2.0'])
 
-        sha = '54e200ab40fc14c863bcc80a51e20b7906608fce'
-
-        cg.find_kernels_with_fixes_subj(sha)
+        cg.find_kernels_with_fixes_subj(TestContextGenerator.linux_sha)
 
         self.assertIn('v1.0', cg.kernels)
         self.assertNotIn('v2.0', cg.kernels)
 
     def test_detect_relevant_commits(self):
-        """Unit test for detect_relevant_commits"""
+        """Unit test for detect_relevant_commits."""
         cg = contextgenerator.ContextGenerator([])
 
+        # From Linux kernel, used for finding commits whose fixes tag refers to it.
         sha = '14fceff4771e51'
 
         cg.detect_relevant_commits(sha)
 
+        # Commit's fixes tag refers to 14fceff4771e51.
         self.assertIn('4d3da2d8d91f66988a829a18a0ce59945e8ae4fb', cg.relevant_commits)
+
+    @mock.patch('cvelib.common.do_pull')
+    def test_filter_based_on_stable(self, _):
+        """Tests that the stable kernels with a given commit are filtered out."""
+        # Tests with $STABLE.
+        cg = contextgenerator.ContextGenerator(['v1.0', 'v2.0'])
+
+        cg.filter_based_on_stable(TestContextGenerator.linux_sha, os.getenv('STABLE'))
+
+        self.assertIn('v1.0', cg.kernels)
+        self.assertNotIn('v2.0', cg.kernels)
+
+        # Tests with $STABLE_RC.
+        cg.filter_based_on_stable(TestContextGenerator.linux_sha, os.getenv('STABLE_RC'))
+
+        self.assertIn('v1.0', cg.kernels)
+        self.assertNotIn('v2.0', cg.kernels)
+
+    def test_is_in_kernel(self):
+        """Tests for properly checking if a given commit is in a given kernel."""
+        cg = contextgenerator.ContextGenerator([])
+
+        check = cg.is_in_kernel(self.kernel_temp1, TestContextGenerator.fixes_subject, True)
+
+        self.assertTrue(check)
+
+        check = cg.is_in_kernel(self.kernel_temp2, TestContextGenerator.fixes_subject, True)
+
+        self.assertFalse(check)
diff --git a/contrib/cvetriager/tests/patchapplier_test.py b/contrib/cvetriager/tests/patchapplier_test.py
index c2f8fd68..56a144f 100644
--- a/contrib/cvetriager/tests/patchapplier_test.py
+++ b/contrib/cvetriager/tests/patchapplier_test.py
@@ -1,6 +1,6 @@
 # -*-coding: utf-8 -*-
 
-"""This is a testing script for cvelib/patchapplier.py"""
+"""Testing script for cvelib/patchapplier.py."""
 
 import unittest
 from unittest import mock
@@ -10,16 +10,16 @@
 
 from cvelib import patchapplier as pa
 
-class TestCVETriager(unittest.TestCase):
-    """Handles test cases for cvelib/patchapplier.py"""
+
+class TestPatchApplier(unittest.TestCase):
+    """Test class for cvelib/patchapplier.py."""
 
     def setUp(self):
-
-        # Backup $LINUX and $CHROMIUMOS_KERNEL
+        # Backup $LINUX and $CHROMIUMOS_KERNEL.
         self.linux = os.getenv('LINUX')
         self.cros_kernel = os.getenv('CHROMIUMOS_KERNEL')
 
-        # Make temporary directory for $LINUX
+        # Make temporary directory for $LINUX.
         self.linux_temp = tempfile.mkdtemp()
         os.environ['LINUX'] = self.linux_temp
         subprocess.check_call(['git', 'init'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
@@ -31,13 +31,13 @@
             subprocess.check_call(['git', 'commit', '-m', str(i)], stdout=subprocess.DEVNULL,
                                   stderr=subprocess.DEVNULL, cwd=os.getenv('LINUX'))
 
-        # Clone LINUX to CHROMIUMOS_KERNEL
+        # Clone LINUX to CHROMIUMOS_KERNEL.
         self.cros_temp = tempfile.mkdtemp()
         os.environ['CHROMIUMOS_KERNEL'] = self.cros_temp
         subprocess.check_call(['git', 'clone', self.linux_temp], stdout=subprocess.DEVNULL,
                               stderr=subprocess.DEVNULL, cwd=os.getenv('CHROMIUMOS_KERNEL'))
 
-        # Add extra commit to LINUX
+        # Add extra commit to LINUX.
         subprocess.check_call(['touch', '4'], cwd=os.getenv('LINUX'))
         subprocess.check_call(['git', 'add', '4'], cwd=os.getenv('LINUX'))
         subprocess.check_call(['git', 'commit', '-m', '4'], stdout=subprocess.DEVNULL,
@@ -57,10 +57,9 @@
         subprocess.check_call(['rm', '-rf', self.linux_temp])
         subprocess.check_call(['rm', '-rf', self.cros_temp])
 
-    @mock.patch('cvelib.patchapplier.checkout_branch')
+    @mock.patch('cvelib.common.checkout_branch')
     def test_apply_patch(self, _):
-        """Unit test for apply_patch"""
-
+        """Unit test for apply_patch."""
         sha = pa.get_sha(self.linux_temp)
         bug_id = '123'
         kernel_versions = [os.path.basename(self.linux_temp)]
@@ -70,8 +69,7 @@
         self.assertTrue(kernels[os.path.basename(self.linux_temp)])
 
     def test_create_commit_message(self):
-        """Unit test for create_commit_message"""
-
+        """Unit test for create_commit_message."""
         kernel = os.path.basename(self.linux_temp)
         kernel_path = os.path.join(os.getenv('CHROMIUMOS_KERNEL'), kernel)
 
@@ -82,7 +80,7 @@
 
         pa.cherry_pick(kernel_path, sha, bug_id)
 
-        # Retrieves new cherry-picked message
+        # Retrieves new cherry-picked message.
         msg = pa.get_commit_message(kernel_path, pa.get_sha(kernel_path))
 
         check = False
@@ -92,8 +90,7 @@
         self.assertTrue(check)
 
     def test_fetch_linux_kernel(self):
-        """Unit test for fetch_linux_kernel"""
-
+        """Unit test for fetch_linux_kernel."""
         kernel = os.path.basename(self.linux_temp)
         kernel_path = os.path.join(os.getenv('CHROMIUMOS_KERNEL'), kernel)
 
@@ -101,12 +98,11 @@
 
         linux_actual = pa.fetch_linux_kernel(kernel_path)
 
-        # Checks if fetched from the correct repo
+        # Checks if fetched from the correct repo.
         self.assertEqual(linux_expected, linux_actual)
 
     def test_cherry_pick(self):
-        """Unit test for cherry_pick"""
-
+        """Unit test for cherry_pick."""
         kernel = os.path.basename(self.linux_temp)
         kernel_path = os.path.join(os.getenv('CHROMIUMOS_KERNEL'), kernel)
 
@@ -119,10 +115,9 @@
 
         self.assertTrue(check)
 
-    @mock.patch('cvelib.patchapplier.checkout_branch')
+    @mock.patch('cvelib.common.checkout_branch')
     def test_invalid_sha(self, _):
-        """Test for passing of invalid commit sha"""
-
+        """Test for passing of invalid commit sha."""
         sha = '123'
         bug = '123'
         kernel_versions = [os.path.basename(self.linux_temp)]
@@ -131,8 +126,7 @@
                           sha, bug, kernel_versions)
 
     def test_invalid_linux_path(self):
-        """Test for invalid LINUX directory"""
-
+        """Test for invalid LINUX directory."""
         linux = os.getenv('LINUX')
         os.environ['LINUX'] = '/tmp/tmp'
 
@@ -145,8 +139,7 @@
         os.environ['LINUX'] = linux
 
     def test_empty_env_variables(self):
-        """Test for empty LINUX or CHROMIUMOS_KERNEL environement variables"""
-
+        """Test for empty LINUX or CHROMIUMOS_KERNEL environement variables."""
         linux = os.getenv('LINUX')
         chros_kernel = os.getenv('CHROMIUMOS_KERNEL')
 
@@ -168,8 +161,7 @@
         os.environ['CHROMIUMOS_KERNEL'] = chros_kernel
 
     def test_invalid_kernel(self):
-        """Test for passing of invalid kernel"""
-
+        """Test for passing of invalid kernel."""
         sha = pa.get_sha(self.linux_temp)
         bug = '123'
         kernel_versions = ['not_a_kernel']