diff --git a/.gitignore b/.gitignore
index 50893bb48f92e1609d3eade183b78ecfab0257b3_LmdpdGlnbm9yZQ==..ba6a254e239393fdd5951d0675216ef7de10ad79_LmdpdGlnbm9yZQ== 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,3 +36,5 @@
 spec/examples.txt
 
 .projections.json
+
+coverage/
diff --git a/Gemfile b/Gemfile
index 50893bb48f92e1609d3eade183b78ecfab0257b3_R2VtZmlsZQ==..ba6a254e239393fdd5951d0675216ef7de10ad79_R2VtZmlsZQ== 100644
--- a/Gemfile
+++ b/Gemfile
@@ -52,4 +52,5 @@
   gem 'rspec_junit_formatter'
   gem 'pry'
   gem 'rspec-parameterized', require: false
+  gem 'simplecov-cobertura'
 end
diff --git a/Gemfile.lock b/Gemfile.lock
index 50893bb48f92e1609d3eade183b78ecfab0257b3_R2VtZmlsZS5sb2Nr..ba6a254e239393fdd5951d0675216ef7de10ad79_R2VtZmlsZS5sb2Nr 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -134,6 +134,7 @@
     cookbook-omnifetch (0.9.1)
       mixlib-archive (>= 0.4, < 2.0)
     diff-lcs (1.3)
+    docile (1.4.0)
     docker-api (2.0.0)
       excon (>= 0.47.0)
       multi_json
@@ -347,6 +348,15 @@
     rubyntlm (0.6.2)
     rubyzip (2.3.0)
     semverse (3.0.0)
+    simplecov (0.21.2)
+      docile (~> 1.1)
+      simplecov-html (~> 0.11)
+      simplecov_json_formatter (~> 0.1)
+    simplecov-cobertura (2.1.0)
+      rexml
+      simplecov (~> 0.19)
+    simplecov-html (0.12.3)
+    simplecov_json_formatter (0.1.4)
     solve (4.0.3)
       molinillo (~> 0.6)
       semverse (>= 1.1, < 4.0)
@@ -456,6 +466,7 @@
   rspec
   rspec-parameterized
   rspec_junit_formatter
+  simplecov-cobertura
   thor (= 0.18.1)
   yard
 
diff --git a/gitlab-ci-config/gitlab-com.yml b/gitlab-ci-config/gitlab-com.yml
index 50893bb48f92e1609d3eade183b78ecfab0257b3_Z2l0bGFiLWNpLWNvbmZpZy9naXRsYWItY29tLnltbA==..ba6a254e239393fdd5951d0675216ef7de10ad79_Z2l0bGFiLWNpLWNvbmZpZy9naXRsYWItY29tLnltbA== 100644
--- a/gitlab-ci-config/gitlab-com.yml
+++ b/gitlab-ci-config/gitlab-com.yml
@@ -128,7 +128,7 @@
   - bundle exec rake cache:bundle
   - bundle exec rake build:component_shas
 
-.spec_template:
+.spec_template: &spec_template
   extends: .gems-cache
   stage: tests
   before_script:
@@ -136,6 +136,18 @@
     # so tests run fine on forks
     - export ALTERNATIVE_SOURCES="true";
     - *install-gems
+  <<: *com-except-triggers-docs-and-deps
+  retry: 1
+  script:
+    - bundle exec rspec --color --format RspecJunitFormatter --out junit_rspec.xml --format documentation spec/lib
+  artifacts:
+    reports: &spec_reports
+      junit: junit_rspec.xml
+
+.chef_spec_template:
+  extends: .spec_template
+  variables:
+    KNAPSACK_TEST_FILE_PATTERN: "spec/chef/**{,/*/**}/*_spec.rb"
   script:
     - JOB_NAME=( $CI_JOB_NAME )
     - export DISTRO_NAME=${JOB_NAME[0]}
@@ -150,8 +162,7 @@
     - rm -f knapsack/*.json
     - mv ${KNAPSACK_REPORT_PATH}.bak ${KNAPSACK_REPORT_PATH}
     - bundle exec rake "knapsack:rspec[--color --format RspecJunitFormatter --out junit_rspec.xml --format documentation]"
-  <<: *com-except-triggers-docs-and-deps
   artifacts:
     <<: *knapsack-artifacts
     reports:
       junit: junit_rspec.xml
@@ -154,8 +165,7 @@
   artifacts:
     <<: *knapsack-artifacts
     reports:
       junit: junit_rspec.xml
-  retry: 1
 
 .trigger-job:
   stage: trigger-package
@@ -418,5 +428,16 @@
 Ubuntu 20.04 knapsack: *prepare_knapsack
 AmazonLinux 2 knapsack: *prepare_knapsack
 
+build library specs:
+  image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-focal"
+  extends: .spec_template
+  needs:
+    - rubocop
+  coverage: '/\(\d+.\d+\%\) covered/'
+  artifacts:
+    reports:
+      <<: *spec_reports
+      cobertura: coverage/coverage.xml
+
 Ubuntu 16.04 specs:
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-xenial"
@@ -421,8 +442,8 @@
 Ubuntu 16.04 specs:
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-xenial"
-  extends: .spec_template
+  extends: .chef_spec_template
   parallel: 6
   needs:
   - Ubuntu 16.04 knapsack
 Ubuntu 18.04 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-bionic"
@@ -424,11 +445,11 @@
   parallel: 6
   needs:
   - Ubuntu 16.04 knapsack
 Ubuntu 18.04 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-bionic"
-  extends: .spec_template
+  extends: .chef_spec_template
   parallel: 6
   needs:
   - Ubuntu 18.04 knapsack
 Ubuntu 20.04 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-focal"
@@ -430,11 +451,11 @@
   parallel: 6
   needs:
   - Ubuntu 18.04 knapsack
 Ubuntu 20.04 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-focal"
-  extends: .spec_template
+  extends: .chef_spec_template
   parallel: 6
   needs:
   - Ubuntu 20.04 knapsack
 Debian 9 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-stretch"
@@ -436,11 +457,11 @@
   parallel: 6
   needs:
   - Ubuntu 20.04 knapsack
 Debian 9 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-stretch"
-  extends: .spec_template
+  extends: .chef_spec_template
   parallel: 6
   needs:
   - Debian 9 knapsack
 Debian 10 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-buster"
@@ -442,11 +463,11 @@
   parallel: 6
   needs:
   - Debian 9 knapsack
 Debian 10 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-buster"
-  extends: .spec_template
+  extends: .chef_spec_template
   parallel: 6
   needs:
   - Debian 10 knapsack
 Debian 11 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-bullseye"
@@ -448,11 +469,11 @@
   parallel: 6
   needs:
   - Debian 10 knapsack
 Debian 11 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-bullseye"
-  extends: .spec_template
+  extends: .chef_spec_template
   parallel: 6
   needs:
   - Debian 11 knapsack
 Centos 7 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-centos7"
@@ -454,11 +475,11 @@
   parallel: 6
   needs:
   - Debian 11 knapsack
 Centos 7 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-centos7"
-  extends: .spec_template
+  extends: .chef_spec_template
   parallel: 6
   needs:
   - Centos 7 knapsack
 Centos 8 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-centos8"
@@ -460,11 +481,11 @@
   parallel: 6
   needs:
   - Centos 7 knapsack
 Centos 8 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-centos8"
-  extends: .spec_template
+  extends: .chef_spec_template
   parallel: 6
   needs:
   - Centos 8 knapsack
 OpenSUSE 15.3 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-opensuse15.3"
@@ -466,11 +487,11 @@
   parallel: 6
   needs:
   - Centos 8 knapsack
 OpenSUSE 15.3 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-opensuse15.3"
-  extends: .spec_template
+  extends: .chef_spec_template
   parallel: 6
   needs:
   - OpenSUSE 15.3 knapsack
 AmazonLinux 2 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-amazonlinux2"
@@ -472,9 +493,9 @@
   parallel: 6
   needs:
   - OpenSUSE 15.3 knapsack
 AmazonLinux 2 specs :
   image: "registry.gitlab.com/gitlab-org/gitlab-build-images:omnibus-gitlab-amazonlinux2"
-  extends: .spec_template
+  extends: .chef_spec_template
   parallel: 6
   needs:
   - AmazonLinux 2 knapsack
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 50893bb48f92e1609d3eade183b78ecfab0257b3_c3BlYy9zcGVjX2hlbHBlci5yYg==..ba6a254e239393fdd5951d0675216ef7de10ad79_c3BlYy9zcGVjX2hlbHBlci5yYg== 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,6 +1,18 @@
 # Just the basic rspec configuration common to all spec testing.
 # Use this if you do not need the full weight of `chef_helper`
 
+require 'simplecov-cobertura'
+
+SimpleCov.start do
+  formatter SimpleCov::Formatter::CoberturaFormatter
+  # SimpleCov does not work for Chef recipes as Chef dynamically loads and
+  # evaluates entire recipes as part of run. This makes SimpleCov think that
+  # everything has been evaluated. Hence, we exclude them.
+  add_filter %r{files/}
+
+  add_group 'Build libraries', 'lib'
+end
+
 require 'rubocop'
 require 'fantaskspec'
 require 'knapsack'
@@ -14,6 +26,9 @@
 $LOAD_PATH << './files/gitlab-ctl-commands/lib'
 
 Knapsack::Adapters::RSpecAdapter.bind if Gitlab::Util.get_env('USE_KNAPSACK')
+Knapsack.report.config({
+                         test_file_pattern: 'spec/chef/**/*_spec.rb'
+                       })
 
 RSpec.configure do |config|
   config.example_status_persistence_file_path = './spec/examples.txt' unless Gitlab::Util.get_env('CI')